Make minimal changes to get HTML files to be valid XHTML, dropping from Strict
[cvs2svn.git] / run-tests.py
blob324d2860f7b67aff4342e1957b23f32bd3526b88
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 # Make sure this Python is recent enough.
48 if sys.hexversion < 0x02020000:
49 sys.stderr.write("error: Python 2.2 or higher required, "
50 "see www.python.org.\n")
51 sys.exit(1)
53 # This script needs to run in the correct directory. Make sure we're there.
54 if not (os.path.exists('cvs2svn') and os.path.exists('test-data')):
55 sys.stderr.write("error: I need to be run in the directory containing "
56 "'cvs2svn' and 'test-data'.\n")
57 sys.exit(1)
59 # Load the Subversion test framework.
60 import svntest
62 # Abbreviations
63 Skip = svntest.testcase.Skip
64 XFail = svntest.testcase.XFail
66 cvs2svn = os.path.abspath('cvs2svn')
68 # We use the installed svn and svnlook binaries, instead of using
69 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
70 # behavior -- or even existence -- of local builds shouldn't affect
71 # the cvs2svn test suite.
72 svn = 'svn'
73 svnlook = 'svnlook'
75 test_data_dir = 'test-data'
76 tmp_dir = 'tmp'
79 #----------------------------------------------------------------------
80 # Helpers.
81 #----------------------------------------------------------------------
84 class RunProgramException:
85 pass
87 class MissingErrorException:
88 pass
90 def run_program(program, error_re, *varargs):
91 """Run PROGRAM with VARARGS, return stdout as a list of lines.
92 If there is any stderr and ERROR_RE is None, raise
93 RunProgramException, and print the stderr lines if
94 svntest.main.verbose_mode is true.
96 If ERROR_RE is not None, it is a string regular expression that must
97 match some line of stderr. If it fails to match, raise
98 MissingErrorExpection."""
99 out, err = svntest.main.run_command(program, 1, 0, *varargs)
100 if err:
101 if error_re:
102 for line in err:
103 if re.match(error_re, line):
104 return out
105 raise MissingErrorException
106 else:
107 if svntest.main.verbose_mode:
108 print '\n%s said:\n' % program
109 for line in err:
110 print ' ' + line,
111 print
112 raise RunProgramException
113 return out
116 def run_cvs2svn(error_re, *varargs):
117 """Run cvs2svn with VARARGS, return stdout as a list of lines.
118 If there is any stderr and ERROR_RE is None, raise
119 RunProgramException, and print the stderr lines if
120 svntest.main.verbose_mode is true.
122 If ERROR_RE is not None, it is a string regular expression that must
123 match some line of stderr. If it fails to match, raise
124 MissingErrorException."""
125 # Use the same python that is running this script
126 return run_program(sys.executable, error_re, cvs2svn, *varargs)
127 # On Windows, for an unknown reason, the cmd.exe process invoked by
128 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
129 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
130 # this. Therefore, the redirection of the output to the .s-revs file fails.
131 # We no longer use the problematic invocation on any system, but this
132 # comment remains to warn about this problem.
135 def run_svn(*varargs):
136 """Run svn with VARARGS; return stdout as a list of lines.
137 If there is any stderr, raise RunProgramException, and print the
138 stderr lines if svntest.main.verbose_mode is true."""
139 return run_program(svn, None, *varargs)
142 def repos_to_url(path_to_svn_repos):
143 """This does what you think it does."""
144 rpath = os.path.abspath(path_to_svn_repos)
145 if rpath[0] != '/':
146 rpath = '/' + rpath
147 return 'file://%s' % string.replace(rpath, os.sep, '/')
149 if hasattr(time, 'strptime'):
150 def svn_strptime(timestr):
151 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
152 else:
153 # This is for Python earlier than 2.3 on Windows
154 _re_rev_date = re.compile(r'(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)')
155 def svn_strptime(timestr):
156 matches = _re_rev_date.match(timestr).groups()
157 return tuple(map(int, matches)) + (0, 1, -1)
159 class Log:
160 def __init__(self, revision, author, date, symbols):
161 self.revision = revision
162 self.author = author
164 # Internally, we represent the date as seconds since epoch (UTC).
165 # Since standard subversion log output shows dates in localtime
167 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
169 # and time.mktime() converts from localtime, it all works out very
170 # happily.
171 self.date = time.mktime(svn_strptime(date[0:19]))
173 # The following symbols are used for string interpolation when
174 # checking paths:
175 self.symbols = symbols
177 # The changed paths will be accumulated later, as log data is read.
178 # Keys here are paths such as '/trunk/foo/bar', values are letter
179 # codes such as 'M', 'A', and 'D'.
180 self.changed_paths = { }
182 # The msg will be accumulated later, as log data is read.
183 self.msg = ''
185 def absorb_changed_paths(self, out):
186 'Read changed paths from OUT into self, until no more.'
187 while 1:
188 line = out.readline()
189 if len(line) == 1: return
190 line = line[:-1]
191 op_portion = line[3:4]
192 path_portion = line[5:]
193 # If we're running on Windows we get backslashes instead of
194 # forward slashes.
195 path_portion = path_portion.replace('\\', '/')
196 # # We could parse out history information, but currently we
197 # # just leave it in the path portion because that's how some
198 # # tests expect it.
200 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
201 # if m:
202 # path_portion = m.group(1)
203 self.changed_paths[path_portion] = op_portion
205 def __cmp__(self, other):
206 return cmp(self.revision, other.revision) or \
207 cmp(self.author, other.author) or cmp(self.date, other.date) or \
208 cmp(self.changed_paths, other.changed_paths) or \
209 cmp(self.msg, other.msg)
211 def get_path_op(self, path):
212 """Return the operator for the change involving PATH.
214 PATH is allowed to include string interpolation directives (e.g.,
215 '%(trunk)s'), which are interpolated against self.symbols. Return
216 None if there is no record for PATH."""
217 return self.changed_paths.get(path % self.symbols)
219 def check_msg(self, msg):
220 """Verify that this Log's message starts with the specified MSG."""
221 if self.msg.find(msg) != 0:
222 raise svntest.Failure(
223 "Revision %d log message was:\n%s\n\n"
224 "It should have begun with:\n%s\n\n"
225 % (self.revision, self.msg, msg,)
228 def check_change(self, path, op):
229 """Verify that this Log includes a change for PATH with operator OP.
231 PATH is allowed to include string interpolation directives (e.g.,
232 '%(trunk)s'), which are interpolated against self.symbols."""
234 path = path % self.symbols
235 found_op = self.changed_paths.get(path, None)
236 if found_op is None:
237 raise svntest.Failure(
238 "Revision %d does not include change for path %s "
239 "(it should have been %s).\n"
240 % (self.revision, path, op,)
242 if found_op != op:
243 raise svntest.Failure(
244 "Revision %d path %s had op %s (it should have been %s)\n"
245 % (self.revision, path, found_op, op,)
248 def check_changes(self, changed_paths):
249 """Verify that this Log has precisely the CHANGED_PATHS specified.
251 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
252 strings are allowed to include string interpolation directives
253 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
255 cp = {}
256 for (path, op) in changed_paths:
257 cp[path % self.symbols] = op
259 if self.changed_paths != cp:
260 raise svntest.Failure(
261 "Revision %d changed paths list was:\n%s\n\n"
262 "It should have been:\n%s\n\n"
263 % (self.revision, self.changed_paths, cp,)
266 def check(self, msg, changed_paths):
267 """Verify that this Log has the MSG and CHANGED_PATHS specified.
269 Convenience function to check two things at once. MSG is passed
270 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
272 self.check_msg(msg)
273 self.check_changes(changed_paths)
276 def parse_log(svn_repos, symbols):
277 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
279 Initialize the Logs' symbols with SYMBOLS."""
281 class LineFeeder:
282 'Make a list of lines behave like an open file handle.'
283 def __init__(self, lines):
284 self.lines = lines
285 def readline(self):
286 if len(self.lines) > 0:
287 return self.lines.pop(0)
288 else:
289 return None
291 def absorb_message_body(out, num_lines, log):
292 'Read NUM_LINES of log message body from OUT into Log item LOG.'
293 i = 0
294 while i < num_lines:
295 line = out.readline()
296 log.msg += line
297 i += 1
299 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
300 '(?P<author>[^\|]+) \| '
301 '(?P<date>[^\|]+) '
302 '\| (?P<lines>[0-9]+) (line|lines)$')
304 log_separator = '-' * 72
306 logs = { }
308 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
310 while 1:
311 this_log = None
312 line = out.readline()
313 if not line: break
314 line = line[:-1]
316 if line.find(log_separator) == 0:
317 line = out.readline()
318 if not line: break
319 line = line[:-1]
320 m = log_start_re.match(line)
321 if m:
322 this_log = Log(
323 int(m.group('rev')), m.group('author'), m.group('date'), symbols)
324 line = out.readline()
325 if not line.find('Changed paths:') == 0:
326 print 'unexpected log output (missing changed paths)'
327 print "Line: '%s'" % line
328 sys.exit(1)
329 this_log.absorb_changed_paths(out)
330 absorb_message_body(out, int(m.group('lines')), this_log)
331 logs[this_log.revision] = this_log
332 elif len(line) == 0:
333 break # We've reached the end of the log output.
334 else:
335 print 'unexpected log output (missing revision line)'
336 print "Line: '%s'" % line
337 sys.exit(1)
338 else:
339 print 'unexpected log output (missing log separator)'
340 print "Line: '%s'" % line
341 sys.exit(1)
343 return logs
346 def erase(path):
347 """Unconditionally remove PATH and its subtree, if any. PATH may be
348 non-existent, a file or symlink, or a directory."""
349 if os.path.isdir(path):
350 svntest.main.safe_rmtree(path)
351 elif os.path.exists(path):
352 os.remove(path)
355 def sym_log_msg(symbolic_name, is_tag=None):
356 """Return the expected log message for a cvs2svn-synthesized revision
357 creating branch or tag SYMBOLIC_NAME."""
358 # This is a copy-paste of part of cvs2svn's make_revision_props
359 if is_tag:
360 type = 'tag'
361 else:
362 type = 'branch'
364 # In Python 2.2.3, we could use textwrap.fill(). Oh well :-).
365 if len(symbolic_name) >= 13:
366 space_or_newline = '\n'
367 else:
368 space_or_newline = ' '
370 log = "This commit was manufactured by cvs2svn to create %s%s'%s'." \
371 % (type, space_or_newline, symbolic_name)
373 return log
376 def make_conversion_id(name, args, passbypass):
377 """Create an identifying tag for a conversion.
379 The return value can also be used as part of a filesystem path.
381 NAME is the name of the CVS repository.
383 ARGS are the extra arguments to be passed to cvs2svn.
385 PASSBYPASS is a boolean indicating whether the conversion is to be
386 run one pass at a time.
388 The 1-to-1 mapping between cvs2svn command parameters and
389 conversion_ids allows us to avoid running the same conversion more
390 than once, when multiple tests use exactly the same conversion."""
392 conv_id = name
394 _win32_fname_mapping = { '/': '_sl_', '\\': '_bs_', ':': '_co_',
395 '*': '_st_', '?': '_qm_', '"': '_qq_',
396 '<': '_lt_', '>': '_gt_', '|': '_pi_', }
397 for arg in args:
398 # Replace some characters that Win32 isn't happy about having in a
399 # filename (which was causing the eol_mime test to fail).
400 sanitized_arg = arg
401 for a, b in _win32_fname_mapping.items():
402 sanitized_arg = sanitized_arg.replace(a, b)
403 conv_id = conv_id + sanitized_arg
405 if passbypass:
406 conv_id = conv_id + '-passbypass'
408 return conv_id
411 class Conversion:
412 """A record of a cvs2svn conversion.
414 Fields:
416 conv_id -- the conversion id for this Conversion.
418 name -- a one-word name indicating the involved repositories.
420 repos -- the path to the svn repository.
422 logs -- a dictionary of Log instances, as returned by parse_log().
424 symbols -- a dictionary of symbols used for string interpolation
425 in path names.
427 _wc -- the basename of the svn working copy (within tmp_dir).
429 _wc_path -- the path to the svn working copy, if it has already
430 been created; otherwise, None. (The working copy is created
431 lazily when get_wc() is called.)
433 _wc_tree -- the tree built from the svn working copy, if it has
434 already been created; otherwise, None. The tree is created
435 lazily when get_wc_tree() is called.)
437 _svnrepos -- the basename of the svn repository (within tmp_dir)."""
439 def __init__(self, conv_id, name, error_re, passbypass, symbols, args):
440 self.conv_id = conv_id
441 self.name = name
442 self.symbols = symbols
443 if not os.path.isdir(tmp_dir):
444 os.mkdir(tmp_dir)
446 cvsrepos = os.path.join('..', test_data_dir, '%s-cvsrepos' % self.name)
448 saved_wd = os.getcwd()
449 try:
450 os.chdir(tmp_dir)
452 self._svnrepos = '%s-svnrepos' % self.conv_id
453 self.repos = os.path.join(tmp_dir, self._svnrepos)
454 self._wc = '%s-wc' % self.conv_id
455 self._wc_path = None
456 self._wc_tree = None
458 # Clean up from any previous invocations of this script.
459 erase(self._svnrepos)
460 erase(self._wc)
462 try:
463 args.extend( [ '--bdb-txn-nosync', '-s', self._svnrepos, cvsrepos ] )
464 if passbypass:
465 for p in range(1, 9):
466 run_cvs2svn(error_re, '-p', str(p), *args)
467 else:
468 run_cvs2svn(error_re, *args)
469 except RunProgramException:
470 raise svntest.Failure
471 except MissingErrorException:
472 raise svntest.Failure("Test failed because no error matched '%s'"
473 % error_re)
475 if not os.path.isdir(self._svnrepos):
476 raise svntest.Failure("Repository not created: '%s'"
477 % os.path.join(os.getcwd(), self._svnrepos))
479 self.logs = parse_log(self._svnrepos, self.symbols)
480 finally:
481 os.chdir(saved_wd)
483 def find_tag_log(self, tagname):
484 """Search LOGS for a log message containing 'TAGNAME' and return the
485 log in which it was found."""
486 for i in xrange(len(self.logs), 0, -1):
487 if self.logs[i].msg.find("'"+tagname+"'") != -1:
488 return self.logs[i]
489 raise ValueError("Tag %s not found in logs" % tagname)
491 def get_wc(self):
492 """Return the path to the svn working copy. If it has not been
493 created yet, create it now."""
494 if self._wc_path is None:
495 saved_wd = os.getcwd()
496 try:
497 os.chdir(tmp_dir)
498 run_svn('co', repos_to_url(self._svnrepos), self._wc)
499 self._wc_path = os.path.join(tmp_dir, self._wc)
500 finally:
501 os.chdir(saved_wd)
502 return self._wc_path
504 def get_wc_tree(self):
505 if self._wc_tree is None:
506 self._wc_tree = svntest.tree.build_tree_from_wc(self.get_wc(), 1)
507 return self._wc_tree
509 def check_props(self, keys, checks):
510 """Helper function for checking lots of properties. For a list of
511 files in the conversion, check that the values of the properties
512 listed in KEYS agree with those listed in CHECKS. CHECKS is a
513 list of tuples: [ (filename, [value, value, ...]), ...], where the
514 values are listed in the same order as the key names are listed in
515 KEYS."""
517 for (file, values) in checks:
518 assert len(values) == len(keys)
519 props = props_for_path(self.get_wc_tree(), file)
520 for i in range(len(keys)):
521 if props.get(keys[i]) != values[i]:
522 raise svntest.Failure(
523 "File %s has property %s set to \"%s\" "
524 "(it should have been \"%s\").\n"
525 % (file, keys[i], props.get(keys[i]), values[i],)
529 # Cache of conversions that have already been done. Keys are conv_id;
530 # values are Conversion instances.
531 already_converted = { }
533 def ensure_conversion(name, error_re=None, passbypass=None,
534 trunk=None, branches=None, tags=None, args=None):
535 """Convert CVS repository NAME to Subversion, but only if it has not
536 been converted before by this invocation of this script. If it has
537 been converted before, return the Conversion object from the
538 previous invocation.
540 If no error, return a Conversion instance.
542 If ERROR_RE is a string, it is a regular expression expected to
543 match some line of stderr printed by the conversion. If there is an
544 error and ERROR_RE is not set, then raise svntest.Failure.
546 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
547 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
549 NAME is just one word. For example, 'main' would mean to convert
550 './test-data/main-cvsrepos', and after the conversion, the resulting
551 Subversion repository would be in './tmp/main-svnrepos', and a
552 checked out head working copy in './tmp/main-wc'.
554 Any other options to pass to cvs2svn should be in ARGS, each element
555 being one option, e.g., '--trunk-only'. If the option takes an
556 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
557 are passed to cvs2svn in the order that they appear in ARGS.
560 if args is None:
561 args = []
562 else:
563 args = list(args)
565 if trunk is None:
566 trunk = 'trunk'
567 else:
568 args.append('--trunk=%s' % (trunk,))
570 if branches is None:
571 branches = 'branches'
572 else:
573 args.append('--branches=%s' % (branches,))
575 if tags is None:
576 tags = 'tags'
577 else:
578 args.append('--tags=%s' % (tags,))
580 conv_id = make_conversion_id(name, args, passbypass)
582 if not already_converted.has_key(conv_id):
583 try:
584 # Run the conversion and store the result for the rest of this
585 # session:
586 already_converted[conv_id] = Conversion(
587 conv_id, name, error_re, passbypass,
588 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
589 args)
590 except svntest.Failure:
591 # Remember the failure so that a future attempt to run this conversion
592 # does not bother to retry, but fails immediately.
593 already_converted[conv_id] = None
594 raise
596 conv = already_converted[conv_id]
597 if conv is None:
598 raise svntest.Failure
599 return conv
602 #----------------------------------------------------------------------
603 # Tests.
604 #----------------------------------------------------------------------
607 def show_usage():
608 "cvs2svn with no arguments shows usage"
609 out = run_cvs2svn(None)
610 if (len(out) > 2 and out[0].find('ERROR:') == 0
611 and out[1].find('DBM module')):
612 print 'cvs2svn cannot execute due to lack of proper DBM module.'
613 print 'Exiting without running any further tests.'
614 sys.exit(1)
615 if out[0].find('USAGE') < 0:
616 raise svntest.Failure('Basic cvs2svn invocation failed.')
619 def show_help_passes():
620 "cvs2svn --help-passes shows pass information"
621 out = run_cvs2svn(None, '--help-passes')
622 if out[0].find('PASSES') < 0:
623 raise svntest.Failure('cvs2svn --help-passes failed.')
626 def attr_exec():
627 "detection of the executable flag"
628 if sys.platform == 'win32':
629 raise svntest.Skip
630 conv = ensure_conversion('main')
631 st = os.stat(
632 os.path.join(conv.get_wc(), 'trunk', 'single-files', 'attr-exec'))
633 if not st[0] & stat.S_IXUSR:
634 raise svntest.Failure
637 def space_fname():
638 "conversion of filename with a space"
639 conv = ensure_conversion('main')
640 if not os.path.exists(
641 os.path.join(conv.get_wc(), 'trunk', 'single-files', 'space fname')):
642 raise svntest.Failure
645 def two_quick():
646 "two commits in quick succession"
647 conv = ensure_conversion('main')
648 logs = parse_log(
649 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
650 if len(logs) != 2:
651 raise svntest.Failure
654 def prune_with_care(**kw):
655 "prune, but never too much"
656 # Robert Pluim encountered this lovely one while converting the
657 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
658 # repository (see issue #1302). Step 4 is the doozy:
660 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
661 # revision 2: adds trunk/blah/NEWS
662 # revision 3: deletes trunk/blah/cookie
663 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
664 # revision 5: does nothing
666 # After fixing cvs2svn, the sequence (correctly) looks like this:
668 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
669 # revision 2: adds trunk/blah/NEWS
670 # revision 3: deletes trunk/blah/cookie
671 # revision 4: does nothing [because trunk/blah/cookie already deleted]
672 # revision 5: deletes blah
674 # The difference is in 4 and 5. In revision 4, it's not correct to
675 # prune blah/, because NEWS is still in there, so revision 4 does
676 # nothing now. But when we delete NEWS in 5, that should bubble up
677 # and prune blah/ instead.
679 # ### Note that empty revisions like 4 are probably going to become
680 # ### at least optional, if not banished entirely from cvs2svn's
681 # ### output. Hmmm, or they may stick around, with an extra
682 # ### revision property explaining what happened. Need to think
683 # ### about that. In some sense, it's a bug in Subversion itself,
684 # ### that such revisions don't show up in 'svn log' output.
686 # In the test below, 'trunk/full-prune/first' represents
687 # cookie, and 'trunk/full-prune/second' represents NEWS.
689 conv = ensure_conversion('main', **kw)
691 # Confirm that revision 4 removes '/trunk/full-prune/first',
692 # and that revision 6 removes '/trunk/full-prune'.
694 # Also confirm similar things about '/full-prune-reappear/...',
695 # which is similar, except that later on it reappears, restored
696 # from pruneland, because a file gets added to it.
698 # And finally, a similar thing for '/partial-prune/...', except that
699 # in its case, a permanent file on the top level prevents the
700 # pruning from going farther than the subdirectory containing first
701 # and second.
703 rev = 11
704 for path in ('/%(trunk)s/full-prune/first',
705 '/%(trunk)s/full-prune-reappear/sub/first',
706 '/%(trunk)s/partial-prune/sub/first'):
707 conv.logs[rev].check_change(path, 'D')
709 rev = 13
710 for path in ('/%(trunk)s/full-prune',
711 '/%(trunk)s/full-prune-reappear',
712 '/%(trunk)s/partial-prune/sub'):
713 conv.logs[rev].check_change(path, 'D')
715 rev = 47
716 for path in ('/%(trunk)s/full-prune-reappear',
717 '/%(trunk)s/full-prune-reappear/appears-later'):
718 conv.logs[rev].check_change(path, 'A')
721 def prune_with_care_variants():
722 "prune, with alternate repo layout"
723 prune_with_care(trunk='a', branches='b', tags='c')
724 prune_with_care(trunk='a/1', branches='b/1', tags='c/1')
725 prune_with_care(trunk='a/1', branches='a/2', tags='a/3')
728 def interleaved_commits():
729 "two interleaved trunk commits, different log msgs"
730 # See test-data/main-cvsrepos/proj/README.
731 conv = ensure_conversion('main')
733 # The initial import.
734 rev = 37
735 conv.logs[rev].check('Initial revision', (
736 ('/%(trunk)s/interleaved', 'A'),
737 ('/%(trunk)s/interleaved/1', 'A'),
738 ('/%(trunk)s/interleaved/2', 'A'),
739 ('/%(trunk)s/interleaved/3', 'A'),
740 ('/%(trunk)s/interleaved/4', 'A'),
741 ('/%(trunk)s/interleaved/5', 'A'),
742 ('/%(trunk)s/interleaved/a', 'A'),
743 ('/%(trunk)s/interleaved/b', 'A'),
744 ('/%(trunk)s/interleaved/c', 'A'),
745 ('/%(trunk)s/interleaved/d', 'A'),
746 ('/%(trunk)s/interleaved/e', 'A'),
749 # This PEP explains why we pass the 'log' parameter to these two
750 # nested functions, instead of just inheriting it from the enclosing
751 # scope: http://www.python.org/peps/pep-0227.html
753 def check_letters(log):
754 """Check if REV is the rev where only letters were committed."""
755 log.check('Committing letters only.', (
756 ('/%(trunk)s/interleaved/a', 'M'),
757 ('/%(trunk)s/interleaved/b', 'M'),
758 ('/%(trunk)s/interleaved/c', 'M'),
759 ('/%(trunk)s/interleaved/d', 'M'),
760 ('/%(trunk)s/interleaved/e', 'M'),
763 def check_numbers(log):
764 """Check if REV is the rev where only numbers were committed."""
765 log.check('Committing numbers only.', (
766 ('/%(trunk)s/interleaved/1', 'M'),
767 ('/%(trunk)s/interleaved/2', 'M'),
768 ('/%(trunk)s/interleaved/3', 'M'),
769 ('/%(trunk)s/interleaved/4', 'M'),
770 ('/%(trunk)s/interleaved/5', 'M'),
773 # One of the commits was letters only, the other was numbers only.
774 # But they happened "simultaneously", so we don't assume anything
775 # about which commit appeared first, so we just try both ways.
776 rev = rev + 3
777 try:
778 check_letters(conv.logs[rev])
779 check_numbers(conv.logs[rev + 1])
780 except svntest.Failure:
781 check_numbers(conv.logs[rev])
782 check_letters(conv.logs[rev + 1])
785 def simple_commits():
786 "simple trunk commits"
787 # See test-data/main-cvsrepos/proj/README.
788 conv = ensure_conversion('main')
790 # The initial import.
791 rev = 23
792 conv.logs[rev].check('Initial revision', (
793 ('/%(trunk)s/proj', 'A'),
794 ('/%(trunk)s/proj/default', 'A'),
795 ('/%(trunk)s/proj/sub1', 'A'),
796 ('/%(trunk)s/proj/sub1/default', 'A'),
797 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
798 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
799 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
800 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
801 ('/%(trunk)s/proj/sub2', 'A'),
802 ('/%(trunk)s/proj/sub2/default', 'A'),
803 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
804 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
805 ('/%(trunk)s/proj/sub3', 'A'),
806 ('/%(trunk)s/proj/sub3/default', 'A'),
809 # The first commit.
810 rev = 30
811 conv.logs[rev].check('First commit to proj, affecting two files.', (
812 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
813 ('/%(trunk)s/proj/sub3/default', 'M'),
816 # The second commit.
817 rev = 31
818 conv.logs[rev].check('Second commit to proj, affecting all 7 files.', (
819 ('/%(trunk)s/proj/default', 'M'),
820 ('/%(trunk)s/proj/sub1/default', 'M'),
821 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
822 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
823 ('/%(trunk)s/proj/sub2/default', 'M'),
824 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
825 ('/%(trunk)s/proj/sub3/default', 'M')
829 def simple_tags(**kw):
830 "simple tags and branches with no commits"
831 # See test-data/main-cvsrepos/proj/README.
832 conv = ensure_conversion('main', **kw)
834 # Verify the copy source for the tags we are about to check
835 # No need to verify the copyfrom revision, as simple_commits did that
836 conv.logs[24].check(sym_log_msg('vendorbranch'), (
837 ('/%(branches)s/vendorbranch/proj (from /%(trunk)s/proj:23)', 'A'),
840 fromstr = ' (from /%(branches)s/vendorbranch:25)'
842 # Tag on rev 1.1.1.1 of all files in proj
843 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
844 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
845 ('/%(tags)s/T_ALL_INITIAL_FILES'+fromstr, 'A'),
846 ('/%(tags)s/T_ALL_INITIAL_FILES/single-files', 'D'),
847 ('/%(tags)s/T_ALL_INITIAL_FILES/partial-prune', 'D'),
850 # The same, as a branch
851 conv.logs[26].check(sym_log_msg('B_FROM_INITIALS'), (
852 ('/%(branches)s/B_FROM_INITIALS'+fromstr, 'A'),
853 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
854 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
857 # Tag on rev 1.1.1.1 of all files in proj, except one
858 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
859 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
860 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr, 'A'),
861 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/single-files', 'D'),
862 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune', 'D'),
863 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
866 # The same, as a branch
867 conv.logs[27].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
868 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE'+fromstr, 'A'),
869 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'),
870 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'),
871 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
875 def simple_tags_variants():
876 "simple tags, with alternate repo layout"
877 simple_tags(trunk='a', branches='b', tags='c')
878 simple_tags(trunk='a/1', branches='b/1', tags='c/1')
879 simple_tags(trunk='a/1', branches='a/2', tags='a/3')
882 def simple_branch_commits():
883 "simple branch commits"
884 # See test-data/main-cvsrepos/proj/README.
885 conv = ensure_conversion('main')
887 rev = 35
888 conv.logs[rev].check('Modify three files, on branch B_MIXED.', (
889 ('/%(branches)s/B_MIXED/proj/default', 'M'),
890 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
891 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
895 def mixed_time_tag():
896 "mixed-time tag"
897 # See test-data/main-cvsrepos/proj/README.
898 conv = ensure_conversion('main')
900 log = conv.find_tag_log('T_MIXED')
901 expected = (
902 ('/%(tags)s/T_MIXED (from /%(trunk)s:31)', 'A'),
903 ('/%(tags)s/T_MIXED/partial-prune', 'D'),
904 ('/%(tags)s/T_MIXED/single-files', 'D'),
905 ('/%(tags)s/T_MIXED/proj/sub2/subsubA '
906 '(from /%(trunk)s/proj/sub2/subsubA:23)', 'R'),
907 ('/%(tags)s/T_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:30)', 'R'),
909 if log.revision == 16:
910 expected.append(('/%(tags)s', 'A'))
911 log.check_changes(expected)
914 def mixed_time_branch_with_added_file():
915 "mixed-time branch, and a file added to the branch"
916 # See test-data/main-cvsrepos/proj/README.
917 conv = ensure_conversion('main')
919 # A branch from the same place as T_MIXED in the previous test,
920 # plus a file added directly to the branch
921 conv.logs[32].check(sym_log_msg('B_MIXED'), (
922 ('/%(branches)s/B_MIXED (from /%(trunk)s:31)', 'A'),
923 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
924 ('/%(branches)s/B_MIXED/single-files', 'D'),
925 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
926 '(from /%(trunk)s/proj/sub2/subsubA:23)', 'R'),
927 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:30)', 'R'),
930 conv.logs[34].check('Add a file on branch B_MIXED.', (
931 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
935 def mixed_commit():
936 "a commit affecting both trunk and a branch"
937 # See test-data/main-cvsrepos/proj/README.
938 conv = ensure_conversion('main')
940 conv.logs[36].check(
941 'A single commit affecting one file on branch B_MIXED '
942 'and one on trunk.', (
943 ('/%(trunk)s/proj/sub2/default', 'M'),
944 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
948 def split_time_branch():
949 "branch some trunk files, and later branch the rest"
950 # See test-data/main-cvsrepos/proj/README.
951 conv = ensure_conversion('main')
953 rev = 42
954 # First change on the branch, creating it
955 conv.logs[rev].check(sym_log_msg('B_SPLIT'), (
956 ('/%(branches)s/B_SPLIT (from /%(trunk)s:36)', 'A'),
957 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
958 ('/%(branches)s/B_SPLIT/single-files', 'D'),
959 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
962 conv.logs[rev + 1].check('First change on branch B_SPLIT.', (
963 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
964 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
965 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
966 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
967 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
970 # A trunk commit for the file which was not branched
971 conv.logs[rev + 2].check('A trunk change to sub1/subsubB/default. '
972 'This was committed about an', (
973 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
976 # Add the file not already branched to the branch, with modification:w
977 conv.logs[rev + 3].check(sym_log_msg('B_SPLIT'), (
978 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
979 '(from /%(trunk)s/proj/sub1/subsubB:44)', 'A'),
982 conv.logs[rev + 4].check('This change affects sub3/default and '
983 'sub1/subsubB/default, on branch', (
984 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
985 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
989 def bogus_tag():
990 "conversion of invalid symbolic names"
991 conv = ensure_conversion('bogus-tag')
994 def overlapping_branch():
995 "ignore a file with a branch with two names"
996 conv = ensure_conversion('overlapping-branch',
997 error_re='.*cannot also have name \'vendorB\'')
998 rev = 4
999 conv.logs[rev].check_change('/%(branches)s/vendorA (from /%(trunk)s:3)',
1000 'A')
1001 # We don't know what order the first two commits would be in, since
1002 # they have different log messages but the same timestamps. As only
1003 # one of the files would be on the vendorB branch in the regression
1004 # case being tested here, we allow for either order.
1005 if (conv.logs[rev].get_path_op(
1006 '/%(branches)s/vendorB (from /%(trunk)s:2)') == 'A'
1007 or conv.logs[rev].get_path_op(
1008 '/%(branches)s/vendorB (from /%(trunk)s:3)') == 'A'):
1009 raise svntest.Failure
1010 conv.logs[rev + 1].check_changes(())
1011 if len(conv.logs) != rev + 1:
1012 raise svntest.Failure
1015 def phoenix_branch(**kw):
1016 "convert a branch file rooted in a 'dead' revision"
1017 conv = ensure_conversion('phoenix', **kw)
1018 conv.logs[8].check(sym_log_msg('volsung_20010721'), (
1019 ('/%(branches)s/volsung_20010721 (from /%(trunk)s:7)', 'A'),
1020 ('/%(branches)s/volsung_20010721/file.txt', 'D'),
1022 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1023 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1027 def phoenix_branch_variants():
1028 "'dead' revision, with alternate repo layout"
1029 phoenix_branch(trunk='a/1', branches='b/1', tags='c/1')
1032 ###TODO: We check for 4 changed paths here to accomodate creating tags
1033 ###and branches in rev 1, but that will change, so this will
1034 ###eventually change back.
1035 def ctrl_char_in_log():
1036 "handle a control char in a log message"
1037 # This was issue #1106.
1038 rev = 2
1039 conv = ensure_conversion('ctrl-char-in-log')
1040 conv.logs[rev].check_changes((
1041 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1043 if conv.logs[rev].msg.find('\x04') < 0:
1044 raise svntest.Failure(
1045 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1048 def overdead():
1049 "handle tags rooted in a redeleted revision"
1050 conv = ensure_conversion('overdead')
1053 def no_trunk_prune(**kw):
1054 "ensure that trunk doesn't get pruned"
1055 conv = ensure_conversion('overdead', **kw)
1056 for rev in conv.logs.keys():
1057 rev_logs = conv.logs[rev]
1058 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1059 raise svntest.Failure
1062 def no_trunk_prune_variants():
1063 "no trunk pruning, with alternate repo layout"
1064 no_trunk_prune(trunk='a', branches='b', tags='c')
1065 no_trunk_prune(trunk='a/1', branches='b/1', tags='c/1')
1066 no_trunk_prune(trunk='a/1', branches='a/2', tags='a/3')
1069 def double_delete():
1070 "file deleted twice, in the root of the repository"
1071 # This really tests several things: how we handle a file that's
1072 # removed (state 'dead') in two successive revisions; how we
1073 # handle a file in the root of the repository (there were some
1074 # bugs in cvs2svn's svn path construction for top-level files); and
1075 # the --no-prune option.
1076 conv = ensure_conversion(
1077 'double-delete', args=['--trunk-only', '--no-prune'])
1079 path = '/%(trunk)s/twice-removed'
1080 rev = 2
1081 conv.logs[rev].check_change(path, 'A')
1082 conv.logs[rev].check_msg('Initial revision')
1084 conv.logs[rev + 1].check_change(path, 'D')
1085 conv.logs[rev + 1].check_msg('Remove this file for the first time.')
1087 if conv.logs[rev + 1].get_path_op('/%(trunk)s') is not None:
1088 raise svntest.Failure
1091 def split_branch():
1092 "branch created from both trunk and another branch"
1093 # See test-data/split-branch-cvsrepos/README.
1095 # The conversion will fail if the bug is present, and
1096 # ensure_conversion will raise svntest.Failure.
1097 conv = ensure_conversion('split-branch')
1100 def resync_misgroups():
1101 "resyncing should not misorder commit groups"
1102 # See test-data/resync-misgroups-cvsrepos/README.
1104 # The conversion will fail if the bug is present, and
1105 # ensure_conversion will raise svntest.Failure.
1106 conv = ensure_conversion('resync-misgroups')
1109 def tagged_branch_and_trunk(**kw):
1110 "allow tags with mixed trunk and branch sources"
1111 conv = ensure_conversion('tagged-branch-n-trunk', **kw)
1113 tags = kw.get('tags', 'tags')
1115 a_path = os.path.join(conv.get_wc(), tags, 'some-tag', 'a.txt')
1116 b_path = os.path.join(conv.get_wc(), tags, 'some-tag', 'b.txt')
1117 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1118 raise svntest.Failure
1119 if (open(a_path, 'r').read().find('1.24') == -1) \
1120 or (open(b_path, 'r').read().find('1.5') == -1):
1121 raise svntest.Failure
1124 def tagged_branch_and_trunk_variants():
1125 "mixed tags, with alternate repo layout"
1126 tagged_branch_and_trunk(trunk='a/1', branches='a/2', tags='a/3')
1129 def enroot_race():
1130 "never use the rev-in-progress as a copy source"
1131 # See issue #1427 and r8544.
1132 conv = ensure_conversion('enroot-race')
1133 rev = 8
1134 conv.logs[rev].check_changes((
1135 ('/%(branches)s/mybranch (from /%(trunk)s:7)', 'A'),
1136 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1137 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1139 conv.logs[rev + 1].check_changes((
1140 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1141 ('/%(trunk)s/proj/a.txt', 'M'),
1142 ('/%(trunk)s/proj/b.txt', 'M'),
1146 def enroot_race_obo():
1147 "do use the last completed rev as a copy source"
1148 conv = ensure_conversion('enroot-race-obo')
1149 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1150 if not len(conv.logs) == 3:
1151 raise svntest.Failure
1154 def branch_delete_first(**kw):
1155 "correctly handle deletion as initial branch action"
1156 # See test-data/branch-delete-first-cvsrepos/README.
1158 # The conversion will fail if the bug is present, and
1159 # ensure_conversion would raise svntest.Failure.
1160 conv = ensure_conversion('branch-delete-first', **kw)
1162 branches = kw.get('branches', 'branches')
1164 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1165 if os.path.exists(
1166 os.path.join(conv.get_wc(), branches, 'branch-1', 'file')):
1167 raise svntest.Failure
1168 if os.path.exists(
1169 os.path.join(conv.get_wc(), branches, 'branch-2', 'file')):
1170 raise svntest.Failure
1171 if not os.path.exists(
1172 os.path.join(conv.get_wc(), branches, 'branch-3', 'file')):
1173 raise svntest.Failure
1176 def branch_delete_first_variants():
1177 "initial delete, with alternate repo layout"
1178 branch_delete_first(trunk='a/1', branches='a/2', tags='a/3')
1181 def nonascii_filenames():
1182 "non ascii files converted incorrectly"
1183 # see issue #1255
1185 # on a en_US.iso-8859-1 machine this test fails with
1186 # svn: Can't recode ...
1188 # as described in the issue
1190 # on a en_US.UTF-8 machine this test fails with
1191 # svn: Malformed XML ...
1193 # which means at least it fails. Unfortunately it won't fail
1194 # with the same error...
1196 # mangle current locale settings so we know we're not running
1197 # a UTF-8 locale (which does not exhibit this problem)
1198 current_locale = locale.getlocale()
1199 new_locale = 'en_US.ISO8859-1'
1200 locale_changed = None
1202 # From http://docs.python.org/lib/module-sys.html
1204 # getfilesystemencoding():
1206 # Return the name of the encoding used to convert Unicode filenames
1207 # into system file names, or None if the system default encoding is
1208 # used. The result value depends on the operating system:
1210 # - On Windows 9x, the encoding is ``mbcs''.
1211 # - On Mac OS X, the encoding is ``utf-8''.
1212 # - On Unix, the encoding is the user's preference according to the
1213 # result of nl_langinfo(CODESET), or None if the
1214 # nl_langinfo(CODESET) failed.
1215 # - On Windows NT+, file names are Unicode natively, so no conversion is
1216 # performed.
1218 # So we're going to skip this test on Mac OS X for now.
1219 if sys.platform == "darwin":
1220 raise svntest.Skip
1222 try:
1223 # change locale to non-UTF-8 locale to generate latin1 names
1224 locale.setlocale(locale.LC_ALL, # this might be too broad?
1225 new_locale)
1226 locale_changed = 1
1227 except locale.Error:
1228 raise svntest.Skip
1230 try:
1231 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
1232 dstrepos_path = os.path.join(test_data_dir,'non-ascii-cvsrepos')
1233 if not os.path.exists(dstrepos_path):
1234 # create repos from existing main repos
1235 shutil.copytree(srcrepos_path, dstrepos_path)
1236 base_path = os.path.join(dstrepos_path, 'single-files')
1237 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1238 os.path.join(base_path, 'two\366uick,v'))
1239 new_path = os.path.join(dstrepos_path, 'single\366files')
1240 os.rename(base_path, new_path)
1242 # if ensure_conversion can generate a
1243 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1244 finally:
1245 if locale_changed:
1246 locale.setlocale(locale.LC_ALL, current_locale)
1247 svntest.main.safe_rmtree(dstrepos_path)
1250 def vendor_branch_sameness():
1251 "avoid spurious changes for initial revs"
1252 conv = ensure_conversion('vendor-branch-sameness')
1254 # There are four files in the repository:
1256 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1257 # the same contents, the file's default branch is 1.1.1,
1258 # and both revisions are in state 'Exp'.
1260 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1261 # 1.1 (the addition of a line of text).
1263 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1265 # d.txt: This file was created by 'cvs add' instead of import, so
1266 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1267 # The timestamp on the add is exactly the same as for the
1268 # imports of the other files.
1270 # (Log messages for the same revisions are the same in all files.)
1272 # What we expect to see is everyone added in r1, then trunk/proj
1273 # copied in r2. In the copy, only a.txt should be left untouched;
1274 # b.txt should be 'M'odified, and (for different reasons) c.txt and
1275 # d.txt should be 'D'eleted.
1277 rev = 2
1278 conv.logs[rev].check('Initial revision', (
1279 ('/%(trunk)s/proj', 'A'),
1280 ('/%(trunk)s/proj/a.txt', 'A'),
1281 ('/%(trunk)s/proj/b.txt', 'A'),
1282 ('/%(trunk)s/proj/c.txt', 'A'),
1283 ('/%(trunk)s/proj/d.txt', 'A'),
1286 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1287 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1288 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1291 conv.logs[rev + 2].check('First vendor branch revision.', (
1292 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1293 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1297 def default_branches():
1298 "handle default branches correctly"
1299 conv = ensure_conversion('default-branches')
1301 # There are seven files in the repository:
1303 # a.txt:
1304 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1305 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1306 # committed (thus losing the default branch "1.1.1"), then
1307 # 1.1.1.4 was imported. All vendor import release tags are
1308 # still present.
1310 # b.txt:
1311 # Like a.txt, but without rev 1.2.
1313 # c.txt:
1314 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1316 # d.txt:
1317 # Same as the previous two, but 1.1.1 branch is unlabeled.
1319 # e.txt:
1320 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1322 # deleted-on-vendor-branch.txt,v:
1323 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1325 # added-then-imported.txt,v:
1326 # Added with 'cvs add' to create 1.1, then imported with
1327 # completely different contents to create 1.1.1.1, therefore
1328 # never had a default branch.
1331 conv.logs[18].check(sym_log_msg('vtag-4',1), (
1332 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:16)', 'A'),
1333 ('/%(tags)s/vtag-4/proj/d.txt '
1334 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:16)', 'A'),
1337 conv.logs[6].check(sym_log_msg('vtag-1',1), (
1338 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:5)', 'A'),
1339 ('/%(tags)s/vtag-1/proj/d.txt '
1340 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1343 conv.logs[9].check(sym_log_msg('vtag-2',1), (
1344 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:7)', 'A'),
1345 ('/%(tags)s/vtag-2/proj/d.txt '
1346 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:7)', 'A'),
1349 conv.logs[12].check(sym_log_msg('vtag-3',1), (
1350 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:10)', 'A'),
1351 ('/%(tags)s/vtag-3/proj/d.txt '
1352 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:10)', 'A'),
1353 ('/%(tags)s/vtag-3/proj/e.txt '
1354 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:10)', 'A'),
1357 conv.logs[17].check("This commit was generated by cvs2svn "
1358 "to compensate for changes in r16,", (
1359 ('/%(trunk)s/proj/b.txt '
1360 '(from /%(branches)s/vbranchA/proj/b.txt:16)', 'R'),
1361 ('/%(trunk)s/proj/c.txt '
1362 '(from /%(branches)s/vbranchA/proj/c.txt:16)', 'R'),
1363 ('/%(trunk)s/proj/d.txt '
1364 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:16)', 'R'),
1365 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1366 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:16)',
1367 'A'),
1368 ('/%(trunk)s/proj/e.txt '
1369 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:16)', 'R'),
1372 conv.logs[16].check("Import (vbranchA, vtag-4).", (
1373 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1374 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1375 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1376 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1377 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1378 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1379 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1382 conv.logs[15].check(sym_log_msg('vbranchA'), (
1383 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1384 '(from /%(trunk)s/proj/added-then-imported.txt:14)', 'A'),
1387 conv.logs[14].check("Add a file to the working copy.", (
1388 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1391 conv.logs[13].check("First regular commit, to a.txt, on vtag-3.", (
1392 ('/%(trunk)s/proj/a.txt', 'M'),
1395 conv.logs[11].check("This commit was generated by cvs2svn "
1396 "to compensate for changes in r10,", (
1397 ('/%(trunk)s/proj/a.txt '
1398 '(from /%(branches)s/vbranchA/proj/a.txt:10)', 'R'),
1399 ('/%(trunk)s/proj/b.txt '
1400 '(from /%(branches)s/vbranchA/proj/b.txt:10)', 'R'),
1401 ('/%(trunk)s/proj/c.txt '
1402 '(from /%(branches)s/vbranchA/proj/c.txt:10)', 'R'),
1403 ('/%(trunk)s/proj/d.txt '
1404 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:10)', 'R'),
1405 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1406 ('/%(trunk)s/proj/e.txt '
1407 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:10)', 'R'),
1410 conv.logs[10].check("Import (vbranchA, vtag-3).", (
1411 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1412 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1413 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1414 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1415 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1416 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1419 conv.logs[8].check("This commit was generated by cvs2svn "
1420 "to compensate for changes in r7,", (
1421 ('/%(trunk)s/proj/a.txt '
1422 '(from /%(branches)s/vbranchA/proj/a.txt:7)', 'R'),
1423 ('/%(trunk)s/proj/b.txt '
1424 '(from /%(branches)s/vbranchA/proj/b.txt:7)', 'R'),
1425 ('/%(trunk)s/proj/c.txt '
1426 '(from /%(branches)s/vbranchA/proj/c.txt:7)', 'R'),
1427 ('/%(trunk)s/proj/d.txt '
1428 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:7)', 'R'),
1429 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1430 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:7)',
1431 'R'),
1432 ('/%(trunk)s/proj/e.txt '
1433 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:7)', 'R'),
1436 conv.logs[7].check("Import (vbranchA, vtag-2).", (
1437 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1438 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1439 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1440 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1441 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1442 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1445 conv.logs[5].check("Import (vbranchA, vtag-1).", ())
1447 conv.logs[4].check(sym_log_msg('vbranchA'), (
1448 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1449 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1450 ('/%(branches)s/vbranchA/proj/e.txt', 'D'),
1453 conv.logs[3].check(sym_log_msg('unlabeled-1.1.1'), (
1454 ('/%(branches)s/unlabeled-1.1.1 (from /%(trunk)s:2)', 'A'),
1455 ('/%(branches)s/unlabeled-1.1.1/proj/a.txt', 'D'),
1456 ('/%(branches)s/unlabeled-1.1.1/proj/b.txt', 'D'),
1457 ('/%(branches)s/unlabeled-1.1.1/proj/c.txt', 'D'),
1458 ('/%(branches)s/unlabeled-1.1.1/proj/deleted-on-vendor-branch.txt', 'D'),
1461 conv.logs[2].check("Initial revision", (
1462 ('/%(trunk)s/proj', 'A'),
1463 ('/%(trunk)s/proj/a.txt', 'A'),
1464 ('/%(trunk)s/proj/b.txt', 'A'),
1465 ('/%(trunk)s/proj/c.txt', 'A'),
1466 ('/%(trunk)s/proj/d.txt', 'A'),
1467 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1468 ('/%(trunk)s/proj/e.txt', 'A'),
1472 def compose_tag_three_sources():
1473 "compose a tag from three sources"
1474 conv = ensure_conversion('compose-tag-three-sources')
1476 conv.logs[2].check("Add on trunk", (
1477 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
1478 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
1479 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
1480 ('/%(trunk)s/tagged-on-b1', 'A'),
1481 ('/%(trunk)s/tagged-on-b2', 'A'),
1484 conv.logs[3].check(sym_log_msg('b1'), (
1485 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
1488 conv.logs[4].check(sym_log_msg('b2'), (
1489 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
1492 conv.logs[5].check("Commit on branch b1", (
1493 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
1494 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
1495 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
1496 ('/%(branches)s/b1/tagged-on-b1', 'M'),
1497 ('/%(branches)s/b1/tagged-on-b2', 'M'),
1500 conv.logs[6].check("Commit on branch b2", (
1501 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
1502 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
1503 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
1504 ('/%(branches)s/b2/tagged-on-b1', 'M'),
1505 ('/%(branches)s/b2/tagged-on-b2', 'M'),
1508 conv.logs[7].check("Commit again on trunk", (
1509 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
1510 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
1511 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
1512 ('/%(trunk)s/tagged-on-b1', 'M'),
1513 ('/%(trunk)s/tagged-on-b2', 'M'),
1516 conv.logs[8].check(sym_log_msg('T',1), (
1517 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
1518 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:7)', 'R'),
1519 ('/%(tags)s/T/tagged-on-trunk-1.1 '
1520 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
1521 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:7)', 'R'),
1525 def pass5_when_to_fill():
1526 "reserve a svn revnum for a fill only when required"
1527 # The conversion will fail if the bug is present, and
1528 # ensure_conversion would raise svntest.Failure.
1529 conv = ensure_conversion('pass5-when-to-fill')
1532 def empty_trunk(**kw):
1533 "don't break when the trunk is empty"
1534 # The conversion will fail if the bug is present, and
1535 # ensure_conversion would raise svntest.Failure.
1536 conv = ensure_conversion('empty-trunk', **kw)
1539 def empty_trunk_variants():
1540 "empty trunk, with alternate repo layout"
1541 empty_trunk(trunk='a', branches='b', tags='c')
1542 empty_trunk(trunk='a/1', branches='a/2', tags='a/3')
1545 def no_spurious_svn_commits():
1546 "ensure that we don't create any spurious commits"
1547 conv = ensure_conversion('phoenix')
1549 # Check spurious commit that could be created in CVSCommit._pre_commit
1550 # (When you add a file on a branch, CVS creates a trunk revision
1551 # in state 'dead'. If the log message of that commit is equal to
1552 # the one that CVS generates, we do not ever create a 'fill'
1553 # SVNCommit for it.)
1555 # and spurious commit that could be created in CVSCommit._commit
1556 # (When you add a file on a branch, CVS creates a trunk revision
1557 # in state 'dead'. If the log message of that commit is equal to
1558 # the one that CVS generates, we do not create a primary SVNCommit
1559 # for it.)
1560 conv.logs[18].check('File added on branch xiphophorus', (
1561 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
1564 # Check to make sure that a commit *is* generated:
1565 # (When you add a file on a branch, CVS creates a trunk revision
1566 # in state 'dead'. If the log message of that commit is NOT equal
1567 # to the one that CVS generates, we create a primary SVNCommit to
1568 # serve as a home for the log message in question.
1569 conv.logs[19].check('file added-on-branch2.txt was initially added on '
1570 + 'branch xiphophorus,\nand this log message was tweaked', ())
1572 # Check spurious commit that could be created in
1573 # CVSRevisionAggregator.attempt_to_commit_symbols
1574 # (We shouldn't consider a CVSRevision whose op is OP_DEAD as a
1575 # candidate for the LastSymbolicNameDatabase.
1576 conv.logs[20].check('This file was also added on branch xiphophorus,', (
1577 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
1581 def peer_path_pruning(**kw):
1582 "make sure that filling prunes paths correctly"
1583 conv = ensure_conversion('peer-path-pruning', **kw)
1584 conv.logs[8].check(sym_log_msg('BRANCH'), (
1585 ('/%(branches)s/BRANCH (from /%(trunk)s:6)', 'A'),
1586 ('/%(branches)s/BRANCH/bar', 'D'),
1587 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:7)', 'R'),
1591 def peer_path_pruning_variants():
1592 "filling prune paths, with alternate repo layout"
1593 peer_path_pruning(trunk='a/1', branches='a/2', tags='a/3')
1596 def invalid_closings_on_trunk():
1597 "verify correct revs are copied to default branches"
1598 # The conversion will fail if the bug is present, and
1599 # ensure_conversion would raise svntest.Failure.
1600 conv = ensure_conversion('invalid-closings-on-trunk')
1603 def individual_passes():
1604 "run each pass individually"
1605 conv = ensure_conversion('main')
1606 conv2 = ensure_conversion('main', passbypass=1)
1608 if conv.logs != conv2.logs:
1609 raise svntest.Failure
1612 def resync_bug():
1613 "reveal a big bug in our resync algorithm"
1614 # This will fail if the bug is present
1615 conv = ensure_conversion('resync-bug')
1618 def branch_from_default_branch():
1619 "reveal a bug in our default branch detection code"
1620 conv = ensure_conversion('branch-from-default-branch')
1622 # This revision will be a default branch synchronization only
1623 # if cvs2svn is correctly determining default branch revisions.
1625 # The bug was that cvs2svn was treating revisions on branches off of
1626 # default branches as default branch revisions, resulting in
1627 # incorrectly regarding the branch off of the default branch as a
1628 # non-trunk default branch. Crystal clear? I thought so. See
1629 # issue #42 for more incoherent blathering.
1630 conv.logs[6].check("This commit was generated by cvs2svn", (
1631 ('/%(trunk)s/proj/file.txt '
1632 '(from /%(branches)s/upstream/proj/file.txt:5)', 'R'),
1635 def file_in_attic_too():
1636 "die if a file exists in and out of the attic"
1637 try:
1638 ensure_conversion('file-in-attic-too')
1639 raise MissingErrorException
1640 except svntest.Failure:
1641 pass
1643 def symbolic_name_filling_guide():
1644 "reveal a big bug in our SymbolicNameFillingGuide"
1645 # This will fail if the bug is present
1646 conv = ensure_conversion('symbolic-name-overfill')
1649 # Helpers for tests involving file contents and properties.
1651 class NodeTreeWalkException:
1652 "Exception class for node tree traversals."
1653 pass
1655 def node_for_path(node, path):
1656 "In the tree rooted under SVNTree NODE, return the node at PATH."
1657 if node.name != '__SVN_ROOT_NODE':
1658 raise NodeTreeWalkException
1659 path = path.strip('/')
1660 components = path.split('/')
1661 for component in components:
1662 node = svntest.tree.get_child(node, component)
1663 return node
1665 # Helper for tests involving properties.
1666 def props_for_path(node, path):
1667 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
1668 return node_for_path(node, path).props
1671 def eol_mime():
1672 "test eol settings and mime types together"
1673 ###TODO: It's a bit klugey to construct this path here. But so far
1674 ### there's only one test with a mime.types file. If we have more,
1675 ### we should abstract this into some helper, which would be located
1676 ### near ensure_conversion(). Note that it is a convention of this
1677 ### test suite for a mime.types file to be located in the top level
1678 ### of the CVS repository to which it applies.
1679 mime_path = os.path.join('..', test_data_dir, 'eol-mime-cvsrepos',
1680 'mime.types')
1682 # We do four conversions. Each time, we pass --mime-types=FILE with
1683 # the same FILE, but vary --no-default-eol and --eol-from-mime-type.
1684 # Thus there's one conversion with neither flag, one with just the
1685 # former, one with just the latter, and one with both.
1687 # In two of the four conversions, we pass --cvs-revnums to make
1688 # certain that there are no bad interactions.
1690 # The files are as follows:
1692 # trunk/foo.txt: no -kb, mime file says nothing.
1693 # trunk/foo.xml: no -kb, mime file says text.
1694 # trunk/foo.zip: no -kb, mime file says non-text.
1695 # trunk/foo.bin: has -kb, mime file says nothing.
1696 # trunk/foo.csv: has -kb, mime file says text.
1697 # trunk/foo.dbf: has -kb, mime file says non-text.
1699 ## Neither --no-default-eol nor --eol-from-mime-type. ##
1700 conv = ensure_conversion(
1701 'eol-mime', args=['--mime-types=%s' % mime_path, '--cvs-revnums'])
1702 conv.check_props(
1703 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1705 ('trunk/foo.txt', ['native', None, '1.2']),
1706 ('trunk/foo.xml', ['native', 'text/xml', '1.2']),
1707 ('trunk/foo.zip', ['native', 'application/zip', '1.2']),
1708 ('trunk/foo.bin', [None, 'application/octet-stream', '1.2']),
1709 ('trunk/foo.csv', [None, 'text/csv', '1.2']),
1710 ('trunk/foo.dbf', [None, 'application/what-is-dbf', '1.2']),
1714 ## Just --no-default-eol, not --eol-from-mime-type. ##
1715 conv = ensure_conversion(
1716 'eol-mime', args=['--mime-types=%s' % mime_path, '--no-default-eol'])
1717 conv.check_props(
1718 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1720 ('trunk/foo.txt', [None, None, None]),
1721 ('trunk/foo.xml', [None, 'text/xml', None]),
1722 ('trunk/foo.zip', [None, 'application/zip', None]),
1723 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
1724 ('trunk/foo.csv', [None, 'text/csv', None]),
1725 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
1729 ## Just --eol-from-mime-type, not --no-default-eol. ##
1730 conv = ensure_conversion('eol-mime', args=[
1731 '--mime-types=%s' % mime_path, '--eol-from-mime-type', '--cvs-revnums'
1733 conv.check_props(
1734 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1736 ('trunk/foo.txt', ['native', None, '1.2']),
1737 ('trunk/foo.xml', ['native', 'text/xml', '1.2']),
1738 ('trunk/foo.zip', [None, 'application/zip', '1.2']),
1739 ('trunk/foo.bin', [None, 'application/octet-stream', '1.2']),
1740 ('trunk/foo.csv', [None, 'text/csv', '1.2']),
1741 ('trunk/foo.dbf', [None, 'application/what-is-dbf', '1.2']),
1745 ## Both --no-default-eol and --eol-from-mime-type. ##
1746 conv = ensure_conversion('eol-mime', args=[
1747 '--mime-types=%s' % mime_path, '--eol-from-mime-type',
1748 '--no-default-eol'])
1749 conv.check_props(
1750 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1752 ('trunk/foo.txt', [None, None, None]),
1753 ('trunk/foo.xml', ['native', 'text/xml', None]),
1754 ('trunk/foo.zip', [None, 'application/zip', None]),
1755 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
1756 ('trunk/foo.csv', [None, 'text/csv', None]),
1757 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
1762 def keywords():
1763 "test setting of svn:keywords property among others"
1764 conv = ensure_conversion('keywords')
1765 conv.check_props(
1766 ['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
1768 ('trunk/foo.default', ['Author Date Id Revision', 'native', None]),
1769 ('trunk/foo.kkvl', ['Author Date Id Revision', 'native', None]),
1770 ('trunk/foo.kkv', ['Author Date Id Revision', 'native', None]),
1771 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
1772 ('trunk/foo.kk', [None, 'native', None]),
1773 ('trunk/foo.ko', [None, 'native', None]),
1774 ('trunk/foo.kv', [None, 'native', None]),
1779 def ignore():
1780 "test setting of svn:ignore property"
1781 conv = ensure_conversion('cvsignore')
1782 wc_tree = conv.get_wc_tree()
1783 topdir_props = props_for_path(wc_tree, 'trunk/proj')
1784 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
1786 if topdir_props['svn:ignore'] != \
1787 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1788 raise svntest.Failure
1790 if subdir_props['svn:ignore'] != \
1791 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1792 raise svntest.Failure
1795 def requires_cvs():
1796 "test that CVS can still do what RCS can't"
1797 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
1798 conv = ensure_conversion('requires-cvs', args=["--use-cvs"])
1800 atsign_contents = file(
1801 os.path.join(conv.get_wc(), "trunk", "atsign-add")).read()
1802 cl_contents = file(
1803 os.path.join(conv.get_wc(), "trunk", "client_lock.idl")).read()
1805 if atsign_contents[-1:] == "@":
1806 raise svntest.Failure
1807 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
1808 raise svntest.Failure
1810 if not (conv.logs[21].author == "William Lyon Phelps III" and
1811 conv.logs[20].author == "j random"):
1812 raise svntest.Failure
1815 def questionable_branch_names():
1816 "test that we can handle weird branch names"
1817 conv = ensure_conversion('questionable-symbols')
1818 # If the conversion succeeds, then we're okay. We could check the
1819 # actual branch paths, too, but the main thing is to know that the
1820 # conversion doesn't fail.
1823 def questionable_tag_names():
1824 "test that we can handle weird tag names"
1825 conv = ensure_conversion('questionable-symbols')
1826 for tag_name in ['Tag_A', 'TagWith--Backslash_E', 'TagWith++Slash_Z']:
1827 conv.find_tag_log(tag_name).check(sym_log_msg(tag_name,1), (
1828 ('/%(tags)s/' + tag_name + ' (from /trunk:8)', 'A'),
1832 def revision_reorder_bug():
1833 "reveal a bug that reorders file revisions"
1834 conv = ensure_conversion('revision-reorder-bug')
1835 # If the conversion succeeds, then we're okay. We could check the
1836 # actual revisions, too, but the main thing is to know that the
1837 # conversion doesn't fail.
1840 def exclude():
1841 "test that exclude really excludes everything"
1842 conv = ensure_conversion('main', args=['--exclude=.*'])
1843 for log in conv.logs.values():
1844 for item in log.changed_paths.keys():
1845 if item.startswith('/branches/') or item.startswith('/tags/'):
1846 raise svntest.Failure
1849 def vendor_branch_delete_add():
1850 "add trunk file that was deleted on vendor branch"
1851 # This will error if the bug is present
1852 conv = ensure_conversion('vendor-branch-delete-add')
1855 def resync_pass2_pull_forward():
1856 "ensure pass2 doesn't pull rev too far forward"
1857 conv = ensure_conversion('resync-pass2-pull-forward')
1858 # If the conversion succeeds, then we're okay. We could check the
1859 # actual revisions, too, but the main thing is to know that the
1860 # conversion doesn't fail.
1863 def native_eol():
1864 "only LFs for svn:eol-style=native files"
1865 conv = ensure_conversion('native-eol')
1866 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
1867 conv.repos)
1868 # Verify that all files in the dump have LF EOLs. We're actually
1869 # testing the whole dump file, but the dump file itself only uses
1870 # LF EOLs, so we're safe.
1871 for line in lines:
1872 if line[-1] != '\n' or line[:-1].find('\r') != -1:
1873 raise svntest.Failure
1876 def double_fill():
1877 "reveal a bug that created a branch twice"
1878 conv = ensure_conversion('double-fill')
1879 # If the conversion succeeds, then we're okay. We could check the
1880 # actual revisions, too, but the main thing is to know that the
1881 # conversion doesn't fail.
1884 def resync_pass2_push_backward():
1885 "ensure pass2 doesn't push rev too far backward"
1886 conv = ensure_conversion('resync-pass2-push-backward')
1887 # If the conversion succeeds, then we're okay. We could check the
1888 # actual revisions, too, but the main thing is to know that the
1889 # conversion doesn't fail.
1892 def double_add():
1893 "reveal a bug that added a branch file twice"
1894 conv = ensure_conversion('double-add')
1895 # If the conversion succeeds, then we're okay. We could check the
1896 # actual revisions, too, but the main thing is to know that the
1897 # conversion doesn't fail.
1900 def bogus_branch_copy():
1901 "reveal a bug that copies a branch file wrongly"
1902 conv = ensure_conversion('bogus-branch-copy')
1903 # If the conversion succeeds, then we're okay. We could check the
1904 # actual revisions, too, but the main thing is to know that the
1905 # conversion doesn't fail.
1908 def nested_ttb_directories():
1909 "require error if ttb directories are not disjoint"
1910 opts_list = [
1911 {'trunk' : 'a', 'branches' : 'a',},
1912 {'trunk' : 'a', 'tags' : 'a',},
1913 {'branches' : 'a', 'tags' : 'a',},
1914 # This option conflicts with the default trunk path:
1915 {'branches' : 'trunk',},
1916 # Try some nested directories:
1917 {'trunk' : 'a', 'branches' : 'a/b',},
1918 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
1919 {'branches' : 'a', 'tags' : 'a/b',},
1922 for opts in opts_list:
1923 try:
1924 ensure_conversion(
1925 'main', error_re=r'.*paths .* and .* are not disjoint\.', **opts
1927 raise MissingErrorException
1928 except svntest.Failure:
1929 pass
1932 def auto_props_ignore_case():
1933 "test auto-props (case-insensitive)"
1934 ### TODO: It's a bit klugey to construct this path here. See also
1935 ### the comment in eol_mime().
1936 auto_props_path = os.path.join('..', test_data_dir, 'eol-mime-cvsrepos',
1937 'auto-props')
1939 # The files are as follows:
1941 # trunk/foo.txt: no -kb, mime auto-prop says nothing.
1942 # trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
1943 # trunk/foo.zip: no -kb, mime auto-prop says non-text.
1944 # trunk/foo.bin: has -kb, mime auto-prop says nothing.
1945 # trunk/foo.csv: has -kb, mime auto-prop says text.
1946 # trunk/foo.dbf: has -kb, mime auto-prop says non-text.
1947 # trunk/foo.UPCASE1: no -kb, no mime type.
1948 # trunk/foo.UPCASE2: no -kb, no mime type.
1950 conv = ensure_conversion(
1951 'eol-mime',
1952 args=['--auto-props=%s' % auto_props_path, '--auto-props-ignore-case'])
1953 conv.check_props(
1954 ['myprop', 'svn:eol-style', 'svn:mime-type'],
1956 ('trunk/foo.txt', ['txt', 'native', None]),
1957 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml']),
1958 ('trunk/foo.zip', ['zip', 'native', 'application/zip']),
1959 ('trunk/foo.bin', ['bin', None, 'application/octet-stream']),
1960 ('trunk/foo.csv', ['csv', None, 'text/csv']),
1961 ('trunk/foo.dbf', ['dbf', None, 'application/what-is-dbf']),
1962 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None]),
1963 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None]),
1968 def auto_props():
1969 "test auto-props (case-sensitive)"
1970 # See auto_props for comments.
1971 auto_props_path = os.path.join('..', test_data_dir, 'eol-mime-cvsrepos',
1972 'auto-props')
1974 conv = ensure_conversion(
1975 'eol-mime', args=['--auto-props=%s' % auto_props_path])
1976 conv.check_props(
1977 ['myprop', 'svn:eol-style', 'svn:mime-type'],
1979 ('trunk/foo.txt', ['txt', 'native', None]),
1980 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml']),
1981 ('trunk/foo.zip', ['zip', 'native', 'application/zip']),
1982 ('trunk/foo.bin', ['bin', None, 'application/octet-stream']),
1983 ('trunk/foo.csv', ['csv', None, 'text/csv']),
1984 ('trunk/foo.dbf', ['dbf', None, 'application/what-is-dbf']),
1985 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None]),
1986 ('trunk/foo.UPCASE2', [None, 'native', None]),
1991 def ctrl_char_in_filename():
1992 "do not allow control characters in filenames"
1994 try:
1995 srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos')
1996 dstrepos_path = os.path.join(test_data_dir,'ctrl-char-filename-cvsrepos')
1997 if os.path.exists(dstrepos_path):
1998 svntest.main.safe_rmtree(dstrepos_path)
2000 # create repos from existing main repos
2001 shutil.copytree(srcrepos_path, dstrepos_path)
2002 base_path = os.path.join(dstrepos_path, 'single-files')
2003 try:
2004 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
2005 os.path.join(base_path, 'two\rquick,v'))
2006 except:
2007 # Operating systems that don't allow control characters in
2008 # filenames will hopefully have thrown an exception; in that
2009 # case, just skip this test.
2010 raise svntest.Skip
2012 try:
2013 conv = ensure_conversion(
2014 'ctrl-char-filename',
2015 error_re=(r'.*Character .* in filename .* '
2016 r'is not supported by subversion\.'),
2018 raise MissingErrorException
2019 except svntest.Failure:
2020 pass
2021 finally:
2022 svntest.main.safe_rmtree(dstrepos_path)
2025 def commit_dependencies():
2026 "interleaved and multi-branch commits to same files"
2027 conv = ensure_conversion("commit-dependencies")
2028 conv.logs[2].check('adding', (
2029 ('/%(trunk)s/interleaved', 'A'),
2030 ('/%(trunk)s/interleaved/file1', 'A'),
2031 ('/%(trunk)s/interleaved/file2', 'A'),
2033 conv.logs[3].check('big commit', (
2034 ('/%(trunk)s/interleaved/file1', 'M'),
2035 ('/%(trunk)s/interleaved/file2', 'M'),
2037 conv.logs[4].check('dependant small commit', (
2038 ('/%(trunk)s/interleaved/file1', 'M'),
2040 conv.logs[5].check('adding', (
2041 ('/%(trunk)s/multi-branch', 'A'),
2042 ('/%(trunk)s/multi-branch/file1', 'A'),
2043 ('/%(trunk)s/multi-branch/file2', 'A'),
2045 conv.logs[6].check(sym_log_msg("branch"), (
2046 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2047 ('/%(branches)s/branch/interleaved', 'D'),
2049 conv.logs[7].check('multi-branch-commit', (
2050 ('/%(trunk)s/multi-branch/file1', 'M'),
2051 ('/%(trunk)s/multi-branch/file2', 'M'),
2052 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2053 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2057 #----------------------------------------------------------------------
2059 ########################################################################
2060 # Run the tests
2062 # list all tests here, starting with None:
2063 test_list = [ None,
2064 show_usage, # 1
2065 attr_exec,
2066 space_fname,
2067 two_quick,
2068 prune_with_care,
2069 interleaved_commits,
2070 simple_commits,
2071 simple_tags,
2072 simple_branch_commits,
2073 mixed_time_tag, # 10
2074 mixed_time_branch_with_added_file,
2075 mixed_commit,
2076 split_time_branch,
2077 bogus_tag,
2078 overlapping_branch,
2079 phoenix_branch,
2080 ctrl_char_in_log,
2081 overdead,
2082 no_trunk_prune,
2083 double_delete, # 20
2084 split_branch,
2085 resync_misgroups,
2086 tagged_branch_and_trunk,
2087 enroot_race,
2088 enroot_race_obo,
2089 branch_delete_first,
2090 nonascii_filenames,
2091 vendor_branch_sameness,
2092 default_branches,
2093 compose_tag_three_sources, # 30
2094 pass5_when_to_fill,
2095 peer_path_pruning,
2096 empty_trunk,
2097 no_spurious_svn_commits,
2098 invalid_closings_on_trunk,
2099 individual_passes,
2100 resync_bug,
2101 branch_from_default_branch,
2102 file_in_attic_too,
2103 symbolic_name_filling_guide, # 40
2104 eol_mime,
2105 keywords,
2106 ignore,
2107 requires_cvs,
2108 questionable_branch_names,
2109 questionable_tag_names,
2110 revision_reorder_bug,
2111 exclude,
2112 vendor_branch_delete_add,
2113 resync_pass2_pull_forward, # 50
2114 native_eol,
2115 double_fill,
2116 resync_pass2_push_backward,
2117 double_add,
2118 bogus_branch_copy,
2119 nested_ttb_directories,
2120 prune_with_care_variants,
2121 simple_tags_variants,
2122 phoenix_branch_variants,
2123 no_trunk_prune_variants, # 60
2124 tagged_branch_and_trunk_variants,
2125 branch_delete_first_variants,
2126 empty_trunk_variants,
2127 peer_path_pruning_variants,
2128 auto_props_ignore_case,
2129 auto_props,
2130 ctrl_char_in_filename,
2131 commit_dependencies,
2132 show_help_passes,
2135 if __name__ == '__main__':
2137 # The Subversion test suite code assumes it's being invoked from
2138 # within a working copy of the Subversion sources, and tries to use
2139 # the binaries in that tree. Since the cvs2svn tree never contains
2140 # a Subversion build, we just use the system's installed binaries.
2141 svntest.main.svn_binary = 'svn'
2142 svntest.main.svnlook_binary = 'svnlook'
2143 svntest.main.svnadmin_binary = 'svnadmin'
2144 svntest.main.svnversion_binary = 'svnversion'
2146 svntest.main.run_tests(test_list)
2147 # NOTREACHED
2150 ### End of file.