Fix spurious testsuite bustage introduced in r1390.
[cvs2svn.git] / run-tests.py
blob7548ffe83c1217b0c67a7e7f6e616c5bf975d15e
1 #!/usr/bin/env python
3 # run_tests.py: test suite for cvs2svn
5 # Subversion is a tool for revision control.
6 # See http://subversion.tigris.org for more information.
7 #
8 # ====================================================================
9 # Copyright (c) 2000-2004 CollabNet. All rights reserved.
11 # This software is licensed as described in the file COPYING, which
12 # you should have received as part of this distribution. The terms
13 # are also available at http://subversion.tigris.org/license-1.html.
14 # If newer versions of this license are posted there, you may use a
15 # newer version instead, at your option.
17 ######################################################################
19 # General modules
20 import sys
21 import shutil
22 import stat
23 import string
24 import re
25 import os
26 import time
27 import os.path
28 import locale
30 # This script needs to run in the correct directory. Make sure we're there.
31 if not (os.path.exists('cvs2svn') and os.path.exists('test-data')):
32 sys.stderr.write("error: I need to be run in the directory containing "
33 "'cvs2svn' and 'test-data'.\n")
34 sys.exit(1)
36 # Load the Subversion test framework.
37 import svntest
39 # Abbreviations
40 Skip = svntest.testcase.Skip
41 XFail = svntest.testcase.XFail
42 Item = svntest.wc.StateItem
44 cvs2svn = os.path.abspath('cvs2svn')
46 # We use the installed svn and svnlook binaries, instead of using
47 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
48 # behavior -- or even existence -- of local builds shouldn't affect
49 # the cvs2svn test suite.
50 svn = 'svn'
51 svnlook = 'svnlook'
53 test_data_dir = 'test-data'
54 tmp_dir = 'tmp'
57 #----------------------------------------------------------------------
58 # Helpers.
59 #----------------------------------------------------------------------
62 class RunProgramException:
63 pass
65 class MissingErrorException:
66 pass
68 def run_program(program, error_re, *varargs):
69 """Run PROGRAM with VARARGS, return stdout as a list of lines.
70 If there is any stderr and ERROR_RE is None, raise
71 RunProgramException, and print the stderr lines if
72 svntest.main.verbose_mode is true.
74 If ERROR_RE is not None, it is a string regular expression that must
75 match some line of stderr. If it fails to match, raise
76 MissingErrorExpection."""
77 out, err = svntest.main.run_command(program, 1, 0, *varargs)
78 if err:
79 if error_re:
80 for line in err:
81 if re.match(error_re, line):
82 return out
83 raise MissingErrorException
84 else:
85 if svntest.main.verbose_mode:
86 print '\n%s said:\n' % program
87 for line in err:
88 print ' ' + line,
89 print
90 raise RunProgramException
91 return out
94 def run_cvs2svn(error_re, *varargs):
95 """Run cvs2svn with VARARGS, return stdout as a list of lines.
96 If there is any stderr and ERROR_RE is None, raise
97 RunProgramException, and print the stderr lines if
98 svntest.main.verbose_mode is true.
100 If ERROR_RE is not None, it is a string regular expression that must
101 match some line of stderr. If it fails to match, raise
102 MissingErrorException."""
103 # Use the same python that is running this script
104 return run_program(sys.executable, error_re, cvs2svn, *varargs)
105 # On Windows, for an unknown reason, the cmd.exe process invoked by
106 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
107 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
108 # this. Therefore, the redirection of the output to the .s-revs file fails.
109 # We no longer use the problematic invocation on any system, but this
110 # comment remains to warn about this problem.
113 def run_svn(*varargs):
114 """Run svn with VARARGS; return stdout as a list of lines.
115 If there is any stderr, raise RunProgramException, and print the
116 stderr lines if svntest.main.verbose_mode is true."""
117 return run_program(svn, None, *varargs)
120 def repos_to_url(path_to_svn_repos):
121 """This does what you think it does."""
122 rpath = os.path.abspath(path_to_svn_repos)
123 if rpath[0] != '/':
124 rpath = '/' + rpath
125 return 'file://%s' % string.replace(rpath, os.sep, '/')
127 if hasattr(time, 'strptime'):
128 def svn_strptime(timestr):
129 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
130 else:
131 # This is for Python earlier than 2.3 on Windows
132 _re_rev_date = re.compile(r'(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)')
133 def svn_strptime(timestr):
134 matches = _re_rev_date.match(timestr).groups()
135 return tuple(map(int, matches)) + (0, 1, -1)
137 class Log:
138 def __init__(self, revision, author, date):
139 self.revision = revision
140 self.author = author
142 # Internally, we represent the date as seconds since epoch (UTC).
143 # Since standard subversion log output shows dates in localtime
145 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
147 # and time.mktime() converts from localtime, it all works out very
148 # happily.
149 self.date = time.mktime(svn_strptime(date[0:19]))
151 # The changed paths will be accumulated later, as log data is read.
152 # Keys here are paths such as '/trunk/foo/bar', values are letter
153 # codes such as 'M', 'A', and 'D'.
154 self.changed_paths = { }
156 # The msg will be accumulated later, as log data is read.
157 self.msg = ''
159 def __cmp__(self, other):
160 return cmp(self.revision, other.revision) or \
161 cmp(self.author, other.author) or cmp(self.date, other.date) or \
162 cmp(self.changed_paths, other.changed_paths) or \
163 cmp(self.msg, other.msg)
166 def parse_log(svn_repos):
167 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS."""
169 class LineFeeder:
170 'Make a list of lines behave like an open file handle.'
171 def __init__(self, lines):
172 self.lines = lines
173 def readline(self):
174 if len(self.lines) > 0:
175 return self.lines.pop(0)
176 else:
177 return None
179 def absorb_changed_paths(out, log):
180 'Read changed paths from OUT into Log item LOG, until no more.'
181 while 1:
182 line = out.readline()
183 if len(line) == 1: return
184 line = line[:-1]
185 op_portion = line[3:4]
186 path_portion = line[5:]
187 # If we're running on Windows we get backslashes instead of
188 # forward slashes.
189 path_portion = path_portion.replace('\\', '/')
190 # # We could parse out history information, but currently we
191 # # just leave it in the path portion because that's how some
192 # # tests expect it.
194 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
195 # if m:
196 # path_portion = m.group(1)
197 log.changed_paths[path_portion] = op_portion
199 def absorb_message_body(out, num_lines, log):
200 'Read NUM_LINES of log message body from OUT into Log item LOG.'
201 i = 0
202 while i < num_lines:
203 line = out.readline()
204 log.msg += line
205 i += 1
207 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
208 '(?P<author>[^\|]+) \| '
209 '(?P<date>[^\|]+) '
210 '\| (?P<lines>[0-9]+) (line|lines)$')
212 log_separator = '-' * 72
214 logs = { }
216 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
218 while 1:
219 this_log = None
220 line = out.readline()
221 if not line: break
222 line = line[:-1]
224 if line.find(log_separator) == 0:
225 line = out.readline()
226 if not line: break
227 line = line[:-1]
228 m = log_start_re.match(line)
229 if m:
230 this_log = Log(int(m.group('rev')), m.group('author'), m.group('date'))
231 line = out.readline()
232 if not line.find('Changed paths:') == 0:
233 print 'unexpected log output (missing changed paths)'
234 print "Line: '%s'" % line
235 sys.exit(1)
236 absorb_changed_paths(out, this_log)
237 absorb_message_body(out, int(m.group('lines')), this_log)
238 logs[this_log.revision] = this_log
239 elif len(line) == 0:
240 break # We've reached the end of the log output.
241 else:
242 print 'unexpected log output (missing revision line)'
243 print "Line: '%s'" % line
244 sys.exit(1)
245 else:
246 print 'unexpected log output (missing log separator)'
247 print "Line: '%s'" % line
248 sys.exit(1)
250 return logs
253 def erase(path):
254 """Unconditionally remove PATH and its subtree, if any. PATH may be
255 non-existent, a file or symlink, or a directory."""
256 if os.path.isdir(path):
257 svntest.main.safe_rmtree(path)
258 elif os.path.exists(path):
259 os.remove(path)
262 def find_tag_rev(logs, tagname):
263 """Search LOGS for a log message containing 'TAGNAME' and return the
264 revision in which it was found."""
265 for i in xrange(len(logs), 0, -1):
266 if logs[i].msg.find("'"+tagname+"'") != -1:
267 return i
268 raise ValueError("Tag %s not found in logs" % tagname)
271 def sym_log_msg(symbolic_name, is_tag=None):
272 """Return the expected log message for a cvs2svn-synthesized revision
273 creating branch or tag SYMBOLIC_NAME."""
274 # This is a copy-paste of part of cvs2svn's make_revision_props
275 if is_tag:
276 type = 'tag'
277 else:
278 type = 'branch'
280 # In Python 2.2.3, we could use textwrap.fill(). Oh well :-).
281 if len(symbolic_name) >= 13:
282 space_or_newline = '\n'
283 else:
284 space_or_newline = ' '
286 log = "This commit was manufactured by cvs2svn to create %s%s'%s'." \
287 % (type, space_or_newline, symbolic_name)
289 return log
292 def check_rev(logs, rev, msg, changed_paths):
293 """Verify the REV of LOGS has the MSG and CHANGED_PATHS specified."""
294 fail = None
295 if logs[rev].msg.find(msg) != 0:
296 print "Revision %d log message was:\n%s\n" % (rev, logs[rev].msg)
297 print "It should have begun with:\n%s\n" % (msg,)
298 fail = 1
299 if logs[rev].changed_paths != changed_paths:
300 print "Revision %d changed paths list was:\n%s\n" % (rev,
301 logs[rev].changed_paths)
302 print "It should have been:\n%s\n" % (changed_paths,)
303 fail = 1
304 if fail:
305 raise svntest.Failure
308 # List of already converted names; see the NAME argument to ensure_conversion.
310 # Keys are names, values are tuples: (svn_repos, svn_wc, log_dictionary).
311 # The log_dictionary comes from parse_log(svn_repos).
312 already_converted = { }
314 def ensure_conversion(name, error_re=None, passbypass=None, *args):
315 """Convert CVS repository NAME to Subversion, but only if it has not
316 been converted before by this invocation of this script. If it has
317 been converted before, do nothing.
319 If no error, return a tuple:
321 svn_repository_path, wc_path, log_dict
323 ...log_dict being the type of dictionary returned by parse_log().
325 If ERROR_RE is a string, it is a regular expression expected to
326 match some line of stderr printed by the conversion. If there is an
327 error and ERROR_RE is not set, then raise svntest.Failure.
329 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
330 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
332 NAME is just one word. For example, 'main' would mean to convert
333 './test-data/main-cvsrepos', and after the conversion, the resulting
334 Subversion repository would be in './tmp/main-svnrepos', and a
335 checked out head working copy in './tmp/main-wc'.
337 Any other options to pass to cvs2svn should be in ARGS, each element
338 being one option, e.g., '--trunk-only'. If the option takes an
339 argument, include it directly, e.g., '--mime-types=PATH'. The order
340 of elements in ARGS does not matter.
343 # Identifying tag for this conversion. This allows us to avoid
344 # running the same conversion more than once, when multiple tests
345 # use exactly the same conversion.
346 conv_id = name
348 # Convert args from tuple to list, then sort them, so we can
349 # construct a reliable conv_id.
350 args = [x for x in args]
351 args.sort()
353 _win32_fname_mapping = { '/': '_sl_', '\\': '_bs_', ':': '_co_', '*': '_st_',
354 '?': '_qm_', '"': '_qq_', '<': '_lt_', '>': '_gt_', '|': '_pi_', }
355 for arg in args:
356 # Replace some characters that Win32 isn't happy about having in a
357 # filename (which was causing the eol_mime test to fail).
358 sanitized_arg = arg
359 for a, b in _win32_fname_mapping.items():
360 sanitized_arg = sanitized_arg.replace(a, b)
361 conv_id = conv_id + sanitized_arg
363 if passbypass:
364 conv_id = conv_id + '-passbypass'
366 if not already_converted.has_key(conv_id):
367 try:
368 if not os.path.isdir(tmp_dir):
369 os.mkdir(tmp_dir)
371 cvsrepos = os.path.abspath(os.path.join(test_data_dir,
372 '%s-cvsrepos' % name))
374 saved_wd = os.getcwd()
375 try:
376 os.chdir(tmp_dir)
378 svnrepos = '%s-svnrepos' % conv_id
379 wc = '%s-wc' % conv_id
381 # Clean up from any previous invocations of this script.
382 erase(svnrepos)
383 erase(wc)
385 try:
386 args.extend( [ '--bdb-txn-nosync', '-s', svnrepos, cvsrepos ] )
387 if passbypass:
388 for p in range(1, 9):
389 apply(run_cvs2svn, [ error_re, '-p', str(p) ] + args)
390 else:
391 ret = apply(run_cvs2svn, [ error_re ] + args)
392 except RunProgramException:
393 raise svntest.Failure
394 except MissingErrorException:
395 print "Test failed because no error matched '%s'" % error_re
396 raise svntest.Failure
398 if not os.path.isdir(svnrepos):
399 print "Repository not created: '%s'" \
400 % os.path.join(os.getcwd(), svnrepos)
401 raise svntest.Failure
403 run_svn('co', repos_to_url(svnrepos), wc)
404 log_dict = parse_log(svnrepos)
405 finally:
406 os.chdir(saved_wd)
408 # This name is done for the rest of this session.
409 already_converted[conv_id] = (os.path.join('tmp', svnrepos),
410 os.path.join('tmp', wc), log_dict)
411 except svntest.Failure:
412 # Remember the failure so that a future attempt to run this conversion
413 # does not bother to retry, but fails immediately.
414 already_converted[conv_id] = None
415 raise
417 if already_converted[conv_id] is None:
418 raise svntest.Failure
419 return already_converted[conv_id]
422 #----------------------------------------------------------------------
423 # Tests.
424 #----------------------------------------------------------------------
427 def show_usage():
428 "cvs2svn with no arguments shows usage"
429 out = run_cvs2svn(None)
430 if (len(out) > 2 and out[0].find('ERROR:') == 0
431 and out[1].find('DBM module')):
432 print 'cvs2svn cannot execute due to lack of proper DBM module.'
433 print 'Exiting without running any further tests.'
434 sys.exit(1)
435 if out[0].find('USAGE') < 0:
436 print 'Basic cvs2svn invocation failed.'
437 raise svntest.Failure
440 def attr_exec():
441 "detection of the executable flag"
442 if sys.platform == 'win32':
443 raise svntest.Skip
444 repos, wc, logs = ensure_conversion('main')
445 st = os.stat(os.path.join(wc, 'trunk', 'single-files', 'attr-exec'))
446 if not st[0] & stat.S_IXUSR:
447 raise svntest.Failure
450 def space_fname():
451 "conversion of filename with a space"
452 repos, wc, logs = ensure_conversion('main')
453 if not os.path.exists(os.path.join(wc, 'trunk', 'single-files',
454 'space fname')):
455 raise svntest.Failure
458 def two_quick():
459 "two commits in quick succession"
460 repos, wc, logs = ensure_conversion('main')
461 logs2 = parse_log(os.path.join(repos, 'trunk', 'single-files', 'twoquick'))
462 if len(logs2) != 2:
463 raise svntest.Failure
466 def prune_with_care():
467 "prune, but never too much"
468 # Robert Pluim encountered this lovely one while converting the
469 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
470 # repository (see issue #1302). Step 4 is the doozy:
472 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
473 # revision 2: adds trunk/blah/NEWS
474 # revision 3: deletes trunk/blah/cookie
475 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
476 # revision 5: does nothing
478 # After fixing cvs2svn, the sequence (correctly) looks like this:
480 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
481 # revision 2: adds trunk/blah/NEWS
482 # revision 3: deletes trunk/blah/cookie
483 # revision 4: does nothing [because trunk/blah/cookie already deleted]
484 # revision 5: deletes blah
486 # The difference is in 4 and 5. In revision 4, it's not correct to
487 # prune blah/, because NEWS is still in there, so revision 4 does
488 # nothing now. But when we delete NEWS in 5, that should bubble up
489 # and prune blah/ instead.
491 # ### Note that empty revisions like 4 are probably going to become
492 # ### at least optional, if not banished entirely from cvs2svn's
493 # ### output. Hmmm, or they may stick around, with an extra
494 # ### revision property explaining what happened. Need to think
495 # ### about that. In some sense, it's a bug in Subversion itself,
496 # ### that such revisions don't show up in 'svn log' output.
498 # In the test below, 'trunk/full-prune/first' represents
499 # cookie, and 'trunk/full-prune/second' represents NEWS.
501 repos, wc, logs = ensure_conversion('main')
503 # Confirm that revision 4 removes '/trunk/full-prune/first',
504 # and that revision 6 removes '/trunk/full-prune'.
506 # Also confirm similar things about '/full-prune-reappear/...',
507 # which is similar, except that later on it reappears, restored
508 # from pruneland, because a file gets added to it.
510 # And finally, a similar thing for '/partial-prune/...', except that
511 # in its case, a permanent file on the top level prevents the
512 # pruning from going farther than the subdirectory containing first
513 # and second.
515 rev = 11
516 for path in ('/trunk/full-prune/first',
517 '/trunk/full-prune-reappear/sub/first',
518 '/trunk/partial-prune/sub/first'):
519 if not (logs[rev].changed_paths.get(path) == 'D'):
520 print "Revision %d failed to remove '%s'." % (rev, path)
521 raise svntest.Failure
523 rev = 13
524 for path in ('/trunk/full-prune',
525 '/trunk/full-prune-reappear',
526 '/trunk/partial-prune/sub'):
527 if not (logs[rev].changed_paths.get(path) == 'D'):
528 print "Revision %d failed to remove '%s'." % (rev, path)
529 raise svntest.Failure
531 rev = 47
532 for path in ('/trunk/full-prune-reappear',
533 '/trunk/full-prune-reappear/appears-later'):
534 if not (logs[rev].changed_paths.get(path) == 'A'):
535 print "Revision %d failed to create path '%s'." % (rev, path)
536 raise svntest.Failure
539 def interleaved_commits():
540 "two interleaved trunk commits, different log msgs"
541 # See test-data/main-cvsrepos/proj/README.
542 repos, wc, logs = ensure_conversion('main')
544 # The initial import.
545 rev = 37
546 for path in ('/trunk/interleaved',
547 '/trunk/interleaved/1',
548 '/trunk/interleaved/2',
549 '/trunk/interleaved/3',
550 '/trunk/interleaved/4',
551 '/trunk/interleaved/5',
552 '/trunk/interleaved/a',
553 '/trunk/interleaved/b',
554 '/trunk/interleaved/c',
555 '/trunk/interleaved/d',
556 '/trunk/interleaved/e',):
557 if not (logs[rev].changed_paths.get(path) == 'A'):
558 raise svntest.Failure
560 if logs[rev].msg.find('Initial revision') != 0:
561 raise svntest.Failure
563 # This PEP explains why we pass the 'logs' parameter to these two
564 # nested functions, instead of just inheriting it from the enclosing
565 # scope: http://www.python.org/peps/pep-0227.html
567 def check_letters(rev, logs):
568 'Return 1 if REV is the rev where only letters were committed, else None.'
569 for path in ('/trunk/interleaved/a',
570 '/trunk/interleaved/b',
571 '/trunk/interleaved/c',
572 '/trunk/interleaved/d',
573 '/trunk/interleaved/e',):
574 if not (logs[rev].changed_paths.get(path) == 'M'):
575 return None
576 if logs[rev].msg.find('Committing letters only.') != 0:
577 return None
578 return 1
580 def check_numbers(rev, logs):
581 'Return 1 if REV is the rev where only numbers were committed, else None.'
582 for path in ('/trunk/interleaved/1',
583 '/trunk/interleaved/2',
584 '/trunk/interleaved/3',
585 '/trunk/interleaved/4',
586 '/trunk/interleaved/5',):
587 if not (logs[rev].changed_paths.get(path) == 'M'):
588 return None
589 if logs[rev].msg.find('Committing numbers only.') != 0:
590 return None
591 return 1
593 # One of the commits was letters only, the other was numbers only.
594 # But they happened "simultaneously", so we don't assume anything
595 # about which commit appeared first, we just try both ways.
596 rev = rev + 3
597 if not ((check_letters(rev, logs) and check_numbers(rev + 1, logs))
598 or (check_numbers(rev, logs) and check_letters(rev + 1, logs))):
599 raise svntest.Failure
602 def simple_commits():
603 "simple trunk commits"
604 # See test-data/main-cvsrepos/proj/README.
605 repos, wc, logs = ensure_conversion('main')
607 # The initial import.
608 rev = 23
609 if not logs[rev].changed_paths == {
610 '/trunk/proj': 'A',
611 '/trunk/proj/default': 'A',
612 '/trunk/proj/sub1': 'A',
613 '/trunk/proj/sub1/default': 'A',
614 '/trunk/proj/sub1/subsubA': 'A',
615 '/trunk/proj/sub1/subsubA/default': 'A',
616 '/trunk/proj/sub1/subsubB': 'A',
617 '/trunk/proj/sub1/subsubB/default': 'A',
618 '/trunk/proj/sub2': 'A',
619 '/trunk/proj/sub2/default': 'A',
620 '/trunk/proj/sub2/subsubA': 'A',
621 '/trunk/proj/sub2/subsubA/default': 'A',
622 '/trunk/proj/sub3': 'A',
623 '/trunk/proj/sub3/default': 'A',
625 raise svntest.Failure
627 if logs[rev].msg.find('Initial revision') != 0:
628 raise svntest.Failure
630 # The first commit.
631 rev = 30
632 if not logs[rev].changed_paths == {
633 '/trunk/proj/sub1/subsubA/default': 'M',
634 '/trunk/proj/sub3/default': 'M',
636 raise svntest.Failure
638 if logs[rev].msg.find('First commit to proj, affecting two files.') != 0:
639 raise svntest.Failure
641 # The second commit.
642 rev = 31
643 if not logs[rev].changed_paths == {
644 '/trunk/proj/default': 'M',
645 '/trunk/proj/sub1/default': 'M',
646 '/trunk/proj/sub1/subsubA/default': 'M',
647 '/trunk/proj/sub1/subsubB/default': 'M',
648 '/trunk/proj/sub2/default': 'M',
649 '/trunk/proj/sub2/subsubA/default': 'M',
650 '/trunk/proj/sub3/default': 'M'
652 raise svntest.Failure
654 if logs[rev].msg.find('Second commit to proj, affecting all 7 files.') != 0:
655 raise svntest.Failure
658 def simple_tags():
659 "simple tags and branches with no commits"
660 # See test-data/main-cvsrepos/proj/README.
661 repos, wc, logs = ensure_conversion('main')
663 # Verify the copy source for the tags we are about to check
664 # No need to verify the copyfrom revision, as simple_commits did that
665 check_rev(logs, 24, sym_log_msg('vendorbranch'), {
666 '/branches/vendorbranch/proj (from /trunk/proj:23)': 'A',
669 fromstr = ' (from /branches/vendorbranch:25)'
671 # Tag on rev 1.1.1.1 of all files in proj
672 rev = find_tag_rev(logs, 'T_ALL_INITIAL_FILES')
673 check_rev(logs, rev, sym_log_msg('T_ALL_INITIAL_FILES',1), {
674 '/tags/T_ALL_INITIAL_FILES'+fromstr: 'A',
675 '/tags/T_ALL_INITIAL_FILES/single-files': 'D',
676 '/tags/T_ALL_INITIAL_FILES/partial-prune': 'D',
679 # The same, as a branch
680 check_rev(logs, 26, sym_log_msg('B_FROM_INITIALS'), {
681 '/branches/B_FROM_INITIALS'+fromstr: 'A',
682 '/branches/B_FROM_INITIALS/single-files': 'D',
683 '/branches/B_FROM_INITIALS/partial-prune': 'D',
686 # Tag on rev 1.1.1.1 of all files in proj, except one
687 rev = find_tag_rev(logs, 'T_ALL_INITIAL_FILES_BUT_ONE')
688 check_rev(logs, rev, sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), {
689 '/tags/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr: 'A',
690 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/single-files': 'D',
691 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune': 'D',
692 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB': 'D',
695 # The same, as a branch
696 check_rev(logs, 27, sym_log_msg('B_FROM_INITIALS_BUT_ONE'), {
697 '/branches/B_FROM_INITIALS_BUT_ONE'+fromstr: 'A',
698 '/branches/B_FROM_INITIALS_BUT_ONE/single-files': 'D',
699 '/branches/B_FROM_INITIALS_BUT_ONE/partial-prune': 'D',
700 '/branches/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB': 'D',
704 def simple_branch_commits():
705 "simple branch commits"
706 # See test-data/main-cvsrepos/proj/README.
707 repos, wc, logs = ensure_conversion('main')
709 rev = 35
710 if not logs[rev].changed_paths == {
711 '/branches/B_MIXED/proj/default': 'M',
712 '/branches/B_MIXED/proj/sub1/default': 'M',
713 '/branches/B_MIXED/proj/sub2/subsubA/default': 'M',
715 raise svntest.Failure
717 if logs[rev].msg.find('Modify three files, on branch B_MIXED.') != 0:
718 raise svntest.Failure
721 def mixed_time_tag():
722 "mixed-time tag"
723 # See test-data/main-cvsrepos/proj/README.
724 repos, wc, logs = ensure_conversion('main')
726 rev = find_tag_rev(logs, 'T_MIXED')
727 expected = {
728 '/tags/T_MIXED (from /trunk:31)': 'A',
729 '/tags/T_MIXED/partial-prune': 'D',
730 '/tags/T_MIXED/single-files': 'D',
731 '/tags/T_MIXED/proj/sub2/subsubA (from /trunk/proj/sub2/subsubA:23)': 'R',
732 '/tags/T_MIXED/proj/sub3 (from /trunk/proj/sub3:30)': 'R',
734 if rev == 16:
735 expected['/tags'] = 'A'
736 if not logs[rev].changed_paths == expected:
737 raise svntest.Failure
740 def mixed_time_branch_with_added_file():
741 "mixed-time branch, and a file added to the branch"
742 # See test-data/main-cvsrepos/proj/README.
743 repos, wc, logs = ensure_conversion('main')
745 # A branch from the same place as T_MIXED in the previous test,
746 # plus a file added directly to the branch
747 check_rev(logs, 32, sym_log_msg('B_MIXED'), {
748 '/branches/B_MIXED (from /trunk:31)': 'A',
749 '/branches/B_MIXED/partial-prune': 'D',
750 '/branches/B_MIXED/single-files': 'D',
751 '/branches/B_MIXED/proj/sub2/subsubA (from /trunk/proj/sub2/subsubA:23)':
752 'R',
753 '/branches/B_MIXED/proj/sub3 (from /trunk/proj/sub3:30)': 'R',
756 check_rev(logs, 34, 'Add a file on branch B_MIXED.', {
757 '/branches/B_MIXED/proj/sub2/branch_B_MIXED_only': 'A',
761 def mixed_commit():
762 "a commit affecting both trunk and a branch"
763 # See test-data/main-cvsrepos/proj/README.
764 repos, wc, logs = ensure_conversion('main')
766 check_rev(logs, 36, 'A single commit affecting one file on branch B_MIXED '
767 'and one on trunk.', {
768 '/trunk/proj/sub2/default': 'M',
769 '/branches/B_MIXED/proj/sub2/branch_B_MIXED_only': 'M',
773 def split_time_branch():
774 "branch some trunk files, and later branch the rest"
775 # See test-data/main-cvsrepos/proj/README.
776 repos, wc, logs = ensure_conversion('main')
778 rev = 42
779 # First change on the branch, creating it
780 check_rev(logs, rev, sym_log_msg('B_SPLIT'), {
781 '/branches/B_SPLIT (from /trunk:36)': 'A',
782 '/branches/B_SPLIT/partial-prune': 'D',
783 '/branches/B_SPLIT/single-files': 'D',
784 '/branches/B_SPLIT/proj/sub1/subsubB': 'D',
787 check_rev(logs, rev + 1, 'First change on branch B_SPLIT.', {
788 '/branches/B_SPLIT/proj/default': 'M',
789 '/branches/B_SPLIT/proj/sub1/default': 'M',
790 '/branches/B_SPLIT/proj/sub1/subsubA/default': 'M',
791 '/branches/B_SPLIT/proj/sub2/default': 'M',
792 '/branches/B_SPLIT/proj/sub2/subsubA/default': 'M',
795 # A trunk commit for the file which was not branched
796 check_rev(logs, rev + 2, 'A trunk change to sub1/subsubB/default. '
797 'This was committed about an', {
798 '/trunk/proj/sub1/subsubB/default': 'M',
801 # Add the file not already branched to the branch, with modification:w
802 check_rev(logs, rev + 3, sym_log_msg('B_SPLIT'), {
803 '/branches/B_SPLIT/proj/sub1/subsubB (from /trunk/proj/sub1/subsubB:44)': 'A',
806 check_rev(logs, rev + 4, 'This change affects sub3/default and '
807 'sub1/subsubB/default, on branch', {
808 '/branches/B_SPLIT/proj/sub1/subsubB/default': 'M',
809 '/branches/B_SPLIT/proj/sub3/default': 'M',
813 def bogus_tag():
814 "conversion of invalid symbolic names"
815 ret, ign, ign = ensure_conversion('bogus-tag')
818 def overlapping_branch():
819 "ignore a file with a branch with two names"
820 repos, wc, logs = ensure_conversion('overlapping-branch',
821 '.*cannot also have name \'vendorB\'')
822 nonlap_path = '/trunk/nonoverlapping-branch'
823 lap_path = '/trunk/overlapping-branch'
824 rev = 4
825 if not (logs[rev].changed_paths.get('/branches/vendorA (from /trunk:3)')
826 == 'A'):
827 raise svntest.Failure
828 # We don't know what order the first two commits would be in, since
829 # they have different log messages but the same timestamps. As only
830 # one of the files would be on the vendorB branch in the regression
831 # case being tested here, we allow for either order.
832 if ((logs[rev].changed_paths.get('/branches/vendorB (from /trunk:2)')
833 == 'A')
834 or (logs[rev].changed_paths.get('/branches/vendorB (from /trunk:3)')
835 == 'A')):
836 raise svntest.Failure
837 if not logs[rev + 1].changed_paths == {}:
838 raise svntest.Failure
839 if len(logs) != rev + 1:
840 raise svntest.Failure
843 def phoenix_branch():
844 "convert a branch file rooted in a 'dead' revision"
845 repos, wc, logs = ensure_conversion('phoenix')
846 check_rev(logs, 8, sym_log_msg('volsung_20010721'), {
847 '/branches/volsung_20010721 (from /trunk:7)': 'A',
848 '/branches/volsung_20010721/file.txt' : 'D'
850 check_rev(logs, 9, 'This file was supplied by Jack Moffitt', {
851 '/branches/volsung_20010721/phoenix': 'A' })
854 ###TODO: We check for 4 changed paths here to accomodate creating tags
855 ###and branches in rev 1, but that will change, so this will
856 ###eventually change back.
857 def ctrl_char_in_log():
858 "handle a control char in a log message"
859 # This was issue #1106.
860 rev = 2
861 repos, wc, logs = ensure_conversion('ctrl-char-in-log')
862 if not ((logs[rev].changed_paths.get('/trunk/ctrl-char-in-log') == 'A')
863 and (len(logs[rev].changed_paths) == 1)):
864 print "Revision 2 of 'ctrl-char-in-log,v' was not converted successfully."
865 raise svntest.Failure
866 if logs[rev].msg.find('\x04') < 0:
867 print "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong."
868 raise svntest.Failure
871 def overdead():
872 "handle tags rooted in a redeleted revision"
873 repos, wc, logs = ensure_conversion('overdead')
876 def no_trunk_prune():
877 "ensure that trunk doesn't get pruned"
878 repos, wc, logs = ensure_conversion('overdead')
879 for rev in logs.keys():
880 rev_logs = logs[rev]
881 for changed_path in rev_logs.changed_paths.keys():
882 if changed_path == '/trunk' \
883 and rev_logs.changed_paths[changed_path] == 'D':
884 raise svntest.Failure
887 def double_delete():
888 "file deleted twice, in the root of the repository"
889 # This really tests several things: how we handle a file that's
890 # removed (state 'dead') in two successive revisions; how we
891 # handle a file in the root of the repository (there were some
892 # bugs in cvs2svn's svn path construction for top-level files); and
893 # the --no-prune option.
894 repos, wc, logs = ensure_conversion('double-delete', None, None,
895 '--trunk-only' , '--no-prune')
897 path = '/trunk/twice-removed'
898 rev = 2
899 if not (logs[rev].changed_paths.get(path) == 'A'):
900 raise svntest.Failure
902 if logs[rev].msg.find('Initial revision') != 0:
903 raise svntest.Failure
905 if not (logs[rev + 1].changed_paths.get(path) == 'D'):
906 raise svntest.Failure
908 if logs[rev + 1].msg.find('Remove this file for the first time.') != 0:
909 raise svntest.Failure
911 if logs[rev + 1].changed_paths.has_key('/trunk'):
912 raise svntest.Failure
915 def split_branch():
916 "branch created from both trunk and another branch"
917 # See test-data/split-branch-cvsrepos/README.
919 # The conversion will fail if the bug is present, and
920 # ensure_conversion would raise svntest.Failure.
921 repos, wc, logs = ensure_conversion('split-branch')
924 def resync_misgroups():
925 "resyncing should not misorder commit groups"
926 # See test-data/resync-misgroups-cvsrepos/README.
928 # The conversion will fail if the bug is present, and
929 # ensure_conversion would raise svntest.Failure.
930 repos, wc, logs = ensure_conversion('resync-misgroups')
933 def tagged_branch_and_trunk():
934 "allow tags with mixed trunk and branch sources"
935 repos, wc, logs = ensure_conversion('tagged-branch-n-trunk')
936 a_path = os.path.join(wc, 'tags', 'some-tag', 'a.txt')
937 b_path = os.path.join(wc, 'tags', 'some-tag', 'b.txt')
938 if not (os.path.exists(a_path) and os.path.exists(b_path)):
939 raise svntest.Failure
940 if (open(a_path, 'r').read().find('1.24') == -1) \
941 or (open(b_path, 'r').read().find('1.5') == -1):
942 raise svntest.Failure
945 def enroot_race():
946 "never use the rev-in-progress as a copy source"
947 # See issue #1427 and r8544.
948 repos, wc, logs = ensure_conversion('enroot-race')
949 rev = 8
950 if not logs[rev].changed_paths == {
951 '/branches/mybranch (from /trunk:7)': 'A',
952 '/branches/mybranch/proj/a.txt': 'D',
953 '/branches/mybranch/proj/b.txt': 'D',
955 raise svntest.Failure
956 if not logs[rev + 1].changed_paths == {
957 '/branches/mybranch/proj/c.txt': 'M',
958 '/trunk/proj/a.txt': 'M',
959 '/trunk/proj/b.txt': 'M',
961 raise svntest.Failure
964 def enroot_race_obo():
965 "do use the last completed rev as a copy source"
966 repos, wc, logs = ensure_conversion('enroot-race-obo')
967 if not ((len(logs) == 3) and
968 (logs[3].changed_paths.get('/branches/BRANCH (from /trunk:2)') == 'A')):
969 raise svntest.Failure
972 def branch_delete_first():
973 "correctly handle deletion as initial branch action"
974 # See test-data/branch-delete-first-cvsrepos/README.
976 # The conversion will fail if the bug is present, and
977 # ensure_conversion would raise svntest.Failure.
978 repos, wc, logs = ensure_conversion('branch-delete-first')
980 # 'file' was deleted from branch-1 and branch-2, but not branch-3
981 if os.path.exists(os.path.join(wc, 'branches', 'branch-1', 'file')):
982 raise svntest.Failure
983 if os.path.exists(os.path.join(wc, 'branches', 'branch-2', 'file')):
984 raise svntest.Failure
985 if not os.path.exists(os.path.join(wc, 'branches', 'branch-3', 'file')):
986 raise svntest.Failure
989 def nonascii_filenames():
990 "non ascii files converted incorrectly"
991 # see issue #1255
993 # on a en_US.iso-8859-1 machine this test fails with
994 # svn: Can't recode ...
996 # as described in the issue
998 # on a en_US.UTF-8 machine this test fails with
999 # svn: Malformed XML ...
1001 # which means at least it fails. Unfortunately it won't fail
1002 # with the same error...
1004 # mangle current locale settings so we know we're not running
1005 # a UTF-8 locale (which does not exhibit this problem)
1006 current_locale = locale.getlocale()
1007 new_locale = 'en_US.ISO8859-1'
1008 locale_changed = None
1010 # From http://docs.python.org/lib/module-sys.html
1012 # getfilesystemencoding():
1014 # Return the name of the encoding used to convert Unicode filenames
1015 # into system file names, or None if the system default encoding is
1016 # used. The result value depends on the operating system:
1018 # - On Windows 9x, the encoding is ``mbcs''.
1019 # - On Mac OS X, the encoding is ``utf-8''.
1020 # - On Unix, the encoding is the user's preference according to the
1021 # result of nl_langinfo(CODESET), or None if the
1022 # nl_langinfo(CODESET) failed.
1023 # - On Windows NT+, file names are Unicode natively, so no conversion is performed.
1025 # So we're going to skip this test on Mac OS X for now.
1026 if sys.platform == "darwin":
1027 raise svntest.Skip
1029 try:
1030 # change locale to non-UTF-8 locale to generate latin1 names
1031 locale.setlocale(locale.LC_ALL, # this might be too broad?
1032 new_locale)
1033 locale_changed = 1
1034 except locale.Error:
1035 raise svntest.Skip
1037 try:
1038 testdata_path = os.path.abspath('test-data')
1039 srcrepos_path = os.path.join(testdata_path,'main-cvsrepos')
1040 dstrepos_path = os.path.join(testdata_path,'non-ascii-cvsrepos')
1041 if not os.path.exists(dstrepos_path):
1042 # create repos from existing main repos
1043 shutil.copytree(srcrepos_path, dstrepos_path)
1044 base_path = os.path.join(dstrepos_path, 'single-files')
1045 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1046 os.path.join(base_path, 'two\366uick,v'))
1047 new_path = os.path.join(dstrepos_path, 'single\366files')
1048 os.rename(base_path, new_path)
1050 # if ensure_conversion can generate a
1051 repos, wc, logs = ensure_conversion('non-ascii', None, None,
1052 '--encoding=latin1')
1053 finally:
1054 if locale_changed:
1055 locale.setlocale(locale.LC_ALL, current_locale)
1056 svntest.main.safe_rmtree(dstrepos_path)
1059 def vendor_branch_sameness():
1060 "avoid spurious changes for initial revs "
1061 repos, wc, logs = ensure_conversion('vendor-branch-sameness')
1063 # There are four files in the repository:
1065 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1066 # the same contents, the file's default branch is 1.1.1,
1067 # and both revisions are in state 'Exp'.
1069 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1070 # 1.1 (the addition of a line of text).
1072 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1074 # d.txt: This file was created by 'cvs add' instead of import, so
1075 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1076 # The timestamp on the add is exactly the same as for the
1077 # imports of the other files.
1079 # (Log messages for the same revisions are the same in all files.)
1081 # What we expect to see is everyone added in r1, then trunk/proj
1082 # copied in r2. In the copy, only a.txt should be left untouched;
1083 # b.txt should be 'M'odified, and (for different reasons) c.txt and
1084 # d.txt should be 'D'eleted.
1086 rev = 2
1087 ###TODO Convert to check_rev.
1088 if logs[rev].msg.find('Initial revision') != 0:
1089 raise svntest.Failure
1091 if not logs[rev].changed_paths == {
1092 '/trunk/proj' : 'A',
1093 '/trunk/proj/a.txt' : 'A',
1094 '/trunk/proj/b.txt' : 'A',
1095 '/trunk/proj/c.txt' : 'A',
1096 '/trunk/proj/d.txt' : 'A',
1098 raise svntest.Failure
1100 if logs[rev + 1].msg.find(sym_log_msg('vbranchA')) != 0:
1101 raise svntest.Failure
1103 if not logs[rev + 1].changed_paths == {
1104 '/branches/vbranchA (from /trunk:2)' : 'A',
1105 '/branches/vbranchA/proj/d.txt' : 'D',
1107 raise svntest.Failure
1109 if logs[rev + 2].msg.find('First vendor branch revision.') != 0:
1110 raise svntest.Failure
1112 if not logs[rev + 2].changed_paths == {
1113 '/branches/vbranchA/proj/b.txt' : 'M',
1114 '/branches/vbranchA/proj/c.txt' : 'D',
1116 raise svntest.Failure
1119 def default_branches():
1120 "handle default branches correctly "
1121 repos, wc, logs = ensure_conversion('default-branches')
1123 # There are seven files in the repository:
1125 # a.txt:
1126 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1127 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1128 # committed (thus losing the default branch "1.1.1"), then
1129 # 1.1.1.4 was imported. All vendor import release tags are
1130 # still present.
1132 # b.txt:
1133 # Like a.txt, but without rev 1.2.
1135 # c.txt:
1136 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1138 # d.txt:
1139 # Same as the previous two, but 1.1.1 branch is unlabeled.
1141 # e.txt:
1142 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1144 # deleted-on-vendor-branch.txt,v:
1145 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1147 # added-then-imported.txt,v:
1148 # Added with 'cvs add' to create 1.1, then imported with
1149 # completely different contents to create 1.1.1.1, therefore
1150 # never had a default branch.
1153 check_rev(logs, 18, sym_log_msg('vtag-4',1), {
1154 '/tags/vtag-4 (from /branches/vbranchA:16)' : 'A',
1155 '/tags/vtag-4/proj/d.txt '
1156 '(from /branches/unlabeled-1.1.1/proj/d.txt:16)' : 'A',
1159 check_rev(logs, 6, sym_log_msg('vtag-1',1), {
1160 '/tags/vtag-1 (from /branches/vbranchA:5)' : 'A',
1161 '/tags/vtag-1/proj/d.txt '
1162 '(from /branches/unlabeled-1.1.1/proj/d.txt:5)' : 'A',
1165 check_rev(logs, 9, sym_log_msg('vtag-2',1), {
1166 '/tags/vtag-2 (from /branches/vbranchA:7)' : 'A',
1167 '/tags/vtag-2/proj/d.txt '
1168 '(from /branches/unlabeled-1.1.1/proj/d.txt:7)' : 'A',
1171 check_rev(logs, 12, sym_log_msg('vtag-3',1), {
1172 '/tags/vtag-3 (from /branches/vbranchA:10)' : 'A',
1173 '/tags/vtag-3/proj/d.txt '
1174 '(from /branches/unlabeled-1.1.1/proj/d.txt:10)' : 'A',
1175 '/tags/vtag-3/proj/e.txt '
1176 '(from /branches/unlabeled-1.1.1/proj/e.txt:10)' : 'A',
1179 check_rev(logs, 17, "This commit was generated by cvs2svn "
1180 "to compensate for changes in r16,", {
1181 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:16)' : 'R',
1182 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:16)' : 'R',
1183 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:16)' : 'R',
1184 '/trunk/proj/deleted-on-vendor-branch.txt '
1185 '(from /branches/vbranchA/proj/deleted-on-vendor-branch.txt:16)' : 'A',
1186 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:16)' : 'R',
1189 check_rev(logs, 16, "Import (vbranchA, vtag-4).", {
1190 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1191 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1192 '/branches/vbranchA/proj/a.txt' : 'M',
1193 '/branches/vbranchA/proj/added-then-imported.txt': 'M', # CHECK!!!
1194 '/branches/vbranchA/proj/b.txt' : 'M',
1195 '/branches/vbranchA/proj/c.txt' : 'M',
1196 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'A',
1199 check_rev(logs, 15, sym_log_msg('vbranchA'), {
1200 '/branches/vbranchA/proj/added-then-imported.txt '
1201 '(from /trunk/proj/added-then-imported.txt:14)' : 'A',
1204 check_rev(logs, 14, "Add a file to the working copy.", {
1205 '/trunk/proj/added-then-imported.txt' : 'A',
1208 check_rev(logs, 13, "First regular commit, to a.txt, on vtag-3.", {
1209 '/trunk/proj/a.txt' : 'M',
1212 check_rev(logs, 11, "This commit was generated by cvs2svn "
1213 "to compensate for changes in r10,", {
1214 '/trunk/proj/a.txt (from /branches/vbranchA/proj/a.txt:10)' : 'R',
1215 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:10)' : 'R',
1216 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:10)' : 'R',
1217 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:10)' : 'R',
1218 '/trunk/proj/deleted-on-vendor-branch.txt' : 'D',
1219 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:10)' : 'R',
1222 check_rev(logs, 10, "Import (vbranchA, vtag-3).", {
1223 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1224 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1225 '/branches/vbranchA/proj/a.txt' : 'M',
1226 '/branches/vbranchA/proj/b.txt' : 'M',
1227 '/branches/vbranchA/proj/c.txt' : 'M',
1228 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'D',
1231 check_rev(logs, 8, "This commit was generated by cvs2svn "
1232 "to compensate for changes in r7,", {
1233 '/trunk/proj/a.txt (from /branches/vbranchA/proj/a.txt:7)' : 'R',
1234 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:7)' : 'R',
1235 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:7)' : 'R',
1236 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:7)' : 'R',
1237 '/trunk/proj/deleted-on-vendor-branch.txt '
1238 '(from /branches/vbranchA/proj/deleted-on-vendor-branch.txt:7)' : 'R',
1239 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:7)' : 'R',
1242 check_rev(logs, 7, "Import (vbranchA, vtag-2).", {
1243 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1244 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1245 '/branches/vbranchA/proj/a.txt' : 'M',
1246 '/branches/vbranchA/proj/b.txt' : 'M',
1247 '/branches/vbranchA/proj/c.txt' : 'M',
1248 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'M',
1251 check_rev(logs, 5, "Import (vbranchA, vtag-1).", {})
1253 check_rev(logs, 4, sym_log_msg('vbranchA'), {
1254 '/branches/vbranchA (from /trunk:2)' : 'A',
1255 '/branches/vbranchA/proj/d.txt' : 'D',
1256 '/branches/vbranchA/proj/e.txt' : 'D',
1259 check_rev(logs, 3, sym_log_msg('unlabeled-1.1.1'), {
1260 '/branches/unlabeled-1.1.1 (from /trunk:2)' : 'A',
1261 '/branches/unlabeled-1.1.1/proj/a.txt' : 'D',
1262 '/branches/unlabeled-1.1.1/proj/b.txt' : 'D',
1263 '/branches/unlabeled-1.1.1/proj/c.txt' : 'D',
1264 '/branches/unlabeled-1.1.1/proj/deleted-on-vendor-branch.txt' : 'D',
1267 check_rev(logs, 2, "Initial revision", {
1268 '/trunk/proj' : 'A',
1269 '/trunk/proj/a.txt' : 'A',
1270 '/trunk/proj/b.txt' : 'A',
1271 '/trunk/proj/c.txt' : 'A',
1272 '/trunk/proj/d.txt' : 'A',
1273 '/trunk/proj/deleted-on-vendor-branch.txt' : 'A',
1274 '/trunk/proj/e.txt' : 'A',
1278 def compose_tag_three_sources():
1279 "compose a tag from three sources"
1280 repos, wc, logs = ensure_conversion('compose-tag-three-sources')
1282 check_rev(logs, 2, "Add on trunk", {
1283 '/trunk/tagged-on-trunk-1.2-a': 'A',
1284 '/trunk/tagged-on-trunk-1.2-b': 'A',
1285 '/trunk/tagged-on-trunk-1.1': 'A',
1286 '/trunk/tagged-on-b1': 'A',
1287 '/trunk/tagged-on-b2': 'A',
1290 check_rev(logs, 3, sym_log_msg('b1'), {
1291 '/branches/b1 (from /trunk:2)': 'A',
1294 check_rev(logs, 4, sym_log_msg('b2'), {
1295 '/branches/b2 (from /trunk:2)': 'A',
1298 check_rev(logs, 5, "Commit on branch b1", {
1299 '/branches/b1/tagged-on-trunk-1.2-a': 'M',
1300 '/branches/b1/tagged-on-trunk-1.2-b': 'M',
1301 '/branches/b1/tagged-on-trunk-1.1': 'M',
1302 '/branches/b1/tagged-on-b1': 'M',
1303 '/branches/b1/tagged-on-b2': 'M',
1306 check_rev(logs, 6, "Commit on branch b2", {
1307 '/branches/b2/tagged-on-trunk-1.2-a': 'M',
1308 '/branches/b2/tagged-on-trunk-1.2-b': 'M',
1309 '/branches/b2/tagged-on-trunk-1.1': 'M',
1310 '/branches/b2/tagged-on-b1': 'M',
1311 '/branches/b2/tagged-on-b2': 'M',
1314 check_rev(logs, 7, "Commit again on trunk", {
1315 '/trunk/tagged-on-trunk-1.2-a': 'M',
1316 '/trunk/tagged-on-trunk-1.2-b': 'M',
1317 '/trunk/tagged-on-trunk-1.1': 'M',
1318 '/trunk/tagged-on-b1': 'M',
1319 '/trunk/tagged-on-b2': 'M',
1322 check_rev(logs, 8, sym_log_msg('T',1), {
1323 '/tags/T (from /trunk:7)': 'A',
1324 '/tags/T/tagged-on-b2 (from /branches/b2/tagged-on-b2:7)': 'R',
1325 '/tags/T/tagged-on-trunk-1.1 (from /trunk/tagged-on-trunk-1.1:2)': 'R',
1326 '/tags/T/tagged-on-b1 (from /branches/b1/tagged-on-b1:7)': 'R',
1330 def pass5_when_to_fill():
1331 "reserve a svn revnum for a fill only when required"
1332 # The conversion will fail if the bug is present, and
1333 # ensure_conversion would raise svntest.Failure.
1334 repos, wc, logs = ensure_conversion('pass5-when-to-fill')
1337 def empty_trunk():
1338 "don't break when the trunk is empty"
1339 # The conversion will fail if the bug is present, and
1340 # ensure_conversion would raise svntest.Failure.
1341 repos, wc, logs = ensure_conversion('empty-trunk')
1343 def no_spurious_svn_commits():
1344 "ensure that we don't create any spurious commits"
1345 repos, wc, logs = ensure_conversion('phoenix')
1347 # Check spurious commit that could be created in CVSCommit._pre_commit
1348 # (When you add a file on a branch, CVS creates a trunk revision
1349 # in state 'dead'. If the log message of that commit is equal to
1350 # the one that CVS generates, we do not ever create a 'fill'
1351 # SVNCommit for it.)
1353 # and spurious commit that could be created in CVSCommit._commit
1354 # (When you add a file on a branch, CVS creates a trunk revision
1355 # in state 'dead'. If the log message of that commit is equal to
1356 # the one that CVS generates, we do not create a primary SVNCommit
1357 # for it.)
1358 check_rev(logs, 18, 'File added on branch xiphophorus', {
1359 '/branches/xiphophorus/added-on-branch.txt' : 'A',
1362 # Check to make sure that a commit *is* generated:
1363 # (When you add a file on a branch, CVS creates a trunk revision
1364 # in state 'dead'. If the log message of that commit is NOT equal
1365 # to the one that CVS generates, we create a primary SVNCommit to
1366 # serve as a home for the log message in question.
1367 check_rev(logs, 19, 'file added-on-branch2.txt was initially added on '
1368 + 'branch xiphophorus,\nand this log message was tweaked', {})
1370 # Check spurious commit that could be created in
1371 # CVSRevisionAggregator.attempt_to_commit_symbols
1372 # (We shouldn't consider a CVSRevision whose op is OP_DEAD as a
1373 # candidate for the LastSymbolicNameDatabase.
1374 check_rev(logs, 20, 'This file was also added on branch xiphophorus,', {
1375 '/branches/xiphophorus/added-on-branch2.txt' : 'A',
1379 def peer_path_pruning():
1380 "make sure that filling prunes paths correctly"
1381 repos, wc, logs = ensure_conversion('peer-path-pruning')
1382 check_rev(logs, 8, sym_log_msg('BRANCH'), {
1383 '/branches/BRANCH (from /trunk:6)' : 'A',
1384 '/branches/BRANCH/bar' : 'D',
1385 '/branches/BRANCH/foo (from /trunk/foo:7)' : 'R',
1388 def invalid_closings_on_trunk():
1389 "verify correct revs are copied to default branches"
1390 # The conversion will fail if the bug is present, and
1391 # ensure_conversion would raise svntest.Failure.
1392 repos, wc, logs = ensure_conversion('invalid-closings-on-trunk')
1395 def individual_passes():
1396 "run each pass individually"
1397 repos, wc, logs = ensure_conversion('main')
1398 repos2, wc2, logs2 = ensure_conversion('main', passbypass=1)
1400 if logs != logs2:
1401 raise svntest.Failure
1404 def resync_bug():
1405 "reveal a big bug in our resync algorithm"
1406 # This will fail if the bug is present
1407 repos, wc, logs = ensure_conversion('resync-bug')
1410 def branch_from_default_branch():
1411 "reveal a bug in our default branch detection code"
1412 repos, wc, logs = ensure_conversion('branch-from-default-branch')
1414 # This revision will be a default branch synchronization only
1415 # if cvs2svn is correctly determining default branch revisions.
1417 # The bug was that cvs2svn was treating revisions on branches off of
1418 # default branches as default branch revisions, resulting in
1419 # incorrectly regarding the branch off of the default branch as a
1420 # non-trunk default branch. Crystal clear? I thought so. See
1421 # issue #42 for more incoherent blathering.
1422 check_rev(logs, 6, "This commit was generated by cvs2svn", {
1423 '/trunk/proj/file.txt (from /branches/upstream/proj/file.txt:5)': 'R',
1426 def file_in_attic_too():
1427 "die if a file exists in and out of the attic"
1428 try:
1429 ensure_conversion('file-in-attic-too')
1430 raise MissingErrorException
1431 except svntest.Failure:
1432 pass
1434 def symbolic_name_filling_guide():
1435 "reveal a big bug in our SymbolicNameFillingGuide"
1436 # This will fail if the bug is present
1437 repos, wc, logs = ensure_conversion('symbolic-name-overfill')
1440 # Helpers for tests involving file contents and properties.
1442 class NodeTreeWalkException:
1443 "Exception class for node tree traversals."
1444 pass
1446 def node_for_path(node, path):
1447 "In the tree rooted under SVNTree NODE, return the node at PATH."
1448 if node.name != '__SVN_ROOT_NODE':
1449 raise NodeTreeWalkException
1450 path = path.strip('/')
1451 components = path.split('/')
1452 for component in components:
1453 node = svntest.tree.get_child(node, component)
1454 return node
1456 # Helper for tests involving properties.
1457 def props_for_path(node, path):
1458 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
1459 return node_for_path(node, path).props
1461 def eol_mime():
1462 "test eol settings and mime types together"
1463 ###TODO: It's a bit klugey to construct this path here. But so far
1464 ### there's only one test with a mime.types file. If we have more,
1465 ### we should abstract this into some helper, which would be located
1466 ### near ensure_conversion(). Note that it is a convention of this
1467 ### test suite for a mime.types file to be located in the top level
1468 ### of the CVS repository to which it applies.
1469 mime_path = os.path.abspath(os.path.join(test_data_dir,
1470 'eol-mime-cvsrepos',
1471 'mime.types'))
1473 def the_usual_suspects(wc_root):
1474 """Return a dictionary mapping files onto their prop dicts.
1475 The files are the six files of interest under WC_ROOT, but with
1476 the 'trunk/' prefix removed. Thus the returned dictionary looks
1477 like this:
1479 'foo.txt' ==> { property dictionary for '/trunk/foo.txt' }
1480 'foo.xml' ==> { property dictionary for '/trunk/foo.xml' }
1481 'foo.zip' ==> { property dictionary for '/trunk/foo.zip' }
1482 'foo.bin' ==> { property dictionary for '/trunk/foo.bin' }
1483 'foo.csv' ==> { property dictionary for '/trunk/foo.csv' }
1484 'foo.dbf' ==> { property dictionary for '/trunk/foo.dbf' }
1486 return {
1487 'foo.txt' : props_for_path(wc_root, 'trunk/foo.txt'),
1488 'foo.xml' : props_for_path(wc_root, 'trunk/foo.xml'),
1489 'foo.zip' : props_for_path(wc_root, 'trunk/foo.zip'),
1490 'foo.bin' : props_for_path(wc_root, 'trunk/foo.bin'),
1491 'foo.csv' : props_for_path(wc_root, 'trunk/foo.csv'),
1492 'foo.dbf' : props_for_path(wc_root, 'trunk/foo.dbf'),
1495 # We do four conversions. Each time, we pass --mime-types=FILE with
1496 # the same FILE, but vary --no-default-eol and --eol-from-mime-type.
1497 # Thus there's one conversion with neither flag, one with just the
1498 # former, one with just the latter, and one with both.
1500 # In two of the four conversions, we pass --cvs-revnums to make
1501 # certain that there are no bad interactions.
1503 ## Neither --no-default-eol nor --eol-from-mime-type. ##
1504 repos, wc, logs = ensure_conversion('eol-mime', None, None,
1505 '--mime-types=%s' % mime_path,
1506 '--cvs-revnums')
1507 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1508 allprops = the_usual_suspects(wc_tree)
1510 # foo.txt (no -kb, mime file says nothing)
1511 if allprops['foo.txt'].get('svn:eol-style') != 'native':
1512 raise svntest.Failure
1513 if allprops['foo.txt'].get('svn:mime-type') is not None:
1514 raise svntest.Failure
1515 if not allprops['foo.txt'].get('cvs2svn:cvs-rev') == '1.2':
1516 raise svntest.Failure
1518 # foo.xml (no -kb, mime file says text)
1519 if allprops['foo.xml'].get('svn:eol-style') != 'native':
1520 raise svntest.Failure
1521 if allprops['foo.xml'].get('svn:mime-type') != 'text/xml':
1522 raise svntest.Failure
1523 if not allprops['foo.xml'].get('cvs2svn:cvs-rev') == '1.2':
1524 raise svntest.Failure
1526 # foo.zip (no -kb, mime file says non-text)
1527 if allprops['foo.zip'].get('svn:eol-style') != 'native':
1528 raise svntest.Failure
1529 if allprops['foo.zip'].get('svn:mime-type') != 'application/zip':
1530 raise svntest.Failure
1531 if not allprops['foo.zip'].get('cvs2svn:cvs-rev') == '1.2':
1532 raise svntest.Failure
1534 # foo.bin (has -kb, mime file says nothing)
1535 if allprops['foo.bin'].get('svn:eol-style') is not None:
1536 raise svntest.Failure
1537 if allprops['foo.bin'].get('svn:mime-type') != 'application/octet-stream':
1538 raise svntest.Failure
1539 if not allprops['foo.bin'].get('cvs2svn:cvs-rev') == '1.2':
1540 raise svntest.Failure
1542 # foo.csv (has -kb, mime file says text)
1543 if allprops['foo.csv'].get('svn:eol-style') is not None:
1544 raise svntest.Failure
1545 if allprops['foo.csv'].get('svn:mime-type') != 'text/csv':
1546 raise svntest.Failure
1547 if not allprops['foo.csv'].get('cvs2svn:cvs-rev') == '1.2':
1548 raise svntest.Failure
1550 # foo.dbf (has -kb, mime file says non-text)
1551 if allprops['foo.dbf'].get('svn:eol-style') is not None:
1552 raise svntest.Failure
1553 if allprops['foo.dbf'].get('svn:mime-type') != 'application/what-is-dbf':
1554 raise svntest.Failure
1555 if not allprops['foo.dbf'].get('cvs2svn:cvs-rev') == '1.2':
1556 raise svntest.Failure
1558 ## Just --no-default-eol, not --eol-from-mime-type. ##
1559 repos, wc, logs = ensure_conversion('eol-mime', None, None,
1560 '--mime-types=%s' % mime_path,
1561 '--no-default-eol')
1562 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1563 allprops = the_usual_suspects(wc_tree)
1565 # foo.txt (no -kb, mime file says nothing)
1566 if allprops['foo.txt'].get('svn:eol-style') is not None:
1567 raise svntest.Failure
1568 if allprops['foo.txt'].get('svn:mime-type') is not None:
1569 raise svntest.Failure
1571 # foo.xml (no -kb, mime file says text)
1572 if allprops['foo.xml'].get('svn:eol-style') is not None:
1573 raise svntest.Failure
1574 if allprops['foo.xml'].get('svn:mime-type') != 'text/xml':
1575 raise svntest.Failure
1577 # foo.zip (no -kb, mime file says non-text)
1578 if allprops['foo.zip'].get('svn:eol-style') is not None:
1579 raise svntest.Failure
1580 if allprops['foo.zip'].get('svn:mime-type') != 'application/zip':
1581 raise svntest.Failure
1583 # foo.bin (has -kb, mime file says nothing)
1584 if allprops['foo.bin'].get('svn:eol-style') is not None:
1585 raise svntest.Failure
1586 if allprops['foo.bin'].get('svn:mime-type') != 'application/octet-stream':
1587 raise svntest.Failure
1589 # foo.csv (has -kb, mime file says text)
1590 if allprops['foo.csv'].get('svn:eol-style') is not None:
1591 raise svntest.Failure
1592 if allprops['foo.csv'].get('svn:mime-type') != 'text/csv':
1593 raise svntest.Failure
1595 # foo.dbf (has -kb, mime file says non-text)
1596 if allprops['foo.dbf'].get('svn:eol-style') is not None:
1597 raise svntest.Failure
1598 if allprops['foo.dbf'].get('svn:mime-type') != 'application/what-is-dbf':
1599 raise svntest.Failure
1601 ## Just --eol-from-mime-type, not --no-default-eol. ##
1602 repos, wc, logs = ensure_conversion('eol-mime', None, None,
1603 '--mime-types=%s' % mime_path,
1604 '--eol-from-mime-type',
1605 '--cvs-revnums')
1606 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1607 allprops = the_usual_suspects(wc_tree)
1609 # foo.txt (no -kb, mime file says nothing)
1610 if allprops['foo.txt'].get('svn:eol-style') != 'native':
1611 raise svntest.Failure
1612 if allprops['foo.txt'].get('svn:mime-type') is not None:
1613 raise svntest.Failure
1614 if not allprops['foo.txt'].get('cvs2svn:cvs-rev') == '1.2':
1615 raise svntest.Failure
1617 # foo.xml (no -kb, mime file says text)
1618 if allprops['foo.xml'].get('svn:eol-style') != 'native':
1619 raise svntest.Failure
1620 if allprops['foo.xml'].get('svn:mime-type') != 'text/xml':
1621 raise svntest.Failure
1622 if not allprops['foo.xml'].get('cvs2svn:cvs-rev') == '1.2':
1623 raise svntest.Failure
1625 # foo.zip (no -kb, mime file says non-text)
1626 if allprops['foo.zip'].get('svn:eol-style') != 'native':
1627 raise svntest.Failure
1628 if allprops['foo.zip'].get('svn:mime-type') != 'application/zip':
1629 raise svntest.Failure
1630 if not allprops['foo.zip'].get('cvs2svn:cvs-rev') == '1.2':
1631 raise svntest.Failure
1633 # foo.bin (has -kb, mime file says nothing)
1634 if allprops['foo.bin'].get('svn:eol-style') is not None:
1635 raise svntest.Failure
1636 if allprops['foo.bin'].get('svn:mime-type') != 'application/octet-stream':
1637 raise svntest.Failure
1638 if not allprops['foo.bin'].get('cvs2svn:cvs-rev') == '1.2':
1639 raise svntest.Failure
1641 # foo.csv (has -kb, mime file says text)
1642 if allprops['foo.csv'].get('svn:eol-style') is not None:
1643 raise svntest.Failure
1644 if allprops['foo.csv'].get('svn:mime-type') != 'text/csv':
1645 raise svntest.Failure
1646 if not allprops['foo.csv'].get('cvs2svn:cvs-rev') == '1.2':
1647 raise svntest.Failure
1649 # foo.dbf (has -kb, mime file says non-text)
1650 if allprops['foo.dbf'].get('svn:eol-style') is not None:
1651 raise svntest.Failure
1652 if allprops['foo.dbf'].get('svn:mime-type') != 'application/what-is-dbf':
1653 raise svntest.Failure
1654 if not allprops['foo.dbf'].get('cvs2svn:cvs-rev') == '1.2':
1655 raise svntest.Failure
1657 ## Both --no-default-eol and --eol-from-mime-type. ##
1658 repos, wc, logs = ensure_conversion('eol-mime', None, None,
1659 '--mime-types=%s' % mime_path,
1660 '--eol-from-mime-type',
1661 '--no-default-eol')
1662 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1663 allprops = the_usual_suspects(wc_tree)
1665 # foo.txt (no -kb, mime file says nothing)
1666 if allprops['foo.txt'].get('svn:eol-style') is not None:
1667 raise svntest.Failure
1668 if allprops['foo.txt'].get('svn:mime-type') is not None:
1669 raise svntest.Failure
1671 # foo.xml (no -kb, mime file says text)
1672 if allprops['foo.xml'].get('svn:eol-style') != 'native':
1673 raise svntest.Failure
1674 if allprops['foo.xml'].get('svn:mime-type') != 'text/xml':
1675 raise svntest.Failure
1677 # foo.zip (no -kb, mime file says non-text)
1678 if allprops['foo.zip'].get('svn:eol-style') is not None:
1679 raise svntest.Failure
1680 if allprops['foo.zip'].get('svn:mime-type') != 'application/zip':
1681 raise svntest.Failure
1683 # foo.bin (has -kb, mime file says nothing)
1684 if allprops['foo.bin'].get('svn:eol-style') is not None:
1685 raise svntest.Failure
1686 if allprops['foo.bin'].get('svn:mime-type') != 'application/octet-stream':
1687 raise svntest.Failure
1689 # foo.csv (has -kb, mime file says text)
1690 if allprops['foo.csv'].get('svn:eol-style') is not None:
1691 raise svntest.Failure
1692 if allprops['foo.csv'].get('svn:mime-type') != 'text/csv':
1693 raise svntest.Failure
1695 # foo.dbf (has -kb, mime file says non-text)
1696 if allprops['foo.dbf'].get('svn:eol-style') is not None:
1697 raise svntest.Failure
1698 if allprops['foo.dbf'].get('svn:mime-type') != 'application/what-is-dbf':
1699 raise svntest.Failure
1702 def check_props(allprops, fname, keywords, eol_style, mime_type):
1703 "helper function for keywords test"
1704 props = allprops[fname]
1705 if (keywords == props.get('svn:keywords') and
1706 eol_style == props.get('svn:eol-style') and
1707 mime_type == props.get('svn:mime-type') ):
1708 pass
1709 else:
1710 print "Unexpected properties for '%s'" % fname
1711 print "keywords:\t%s\t%s" % (keywords, props.get('svn:keywords'))
1712 print "eol-style:\t%s\t%s" % (eol_style, props.get('svn:eol-style'))
1713 print "mime-type:\t%s\t%s" % (mime_type, props.get('svn:mime-type'))
1714 raise svntest.Failure
1716 def keywords():
1717 "test setting of svn:keywords property among others"
1718 repos, wc, logs = ensure_conversion('keywords')
1719 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1720 allprops = {
1721 'foo.default' : props_for_path(wc_tree, '/trunk/foo.default'),
1722 'foo.kkvl' : props_for_path(wc_tree, '/trunk/foo.kkvl'),
1723 'foo.kkv' : props_for_path(wc_tree, '/trunk/foo.kkv'),
1724 'foo.kb' : props_for_path(wc_tree, '/trunk/foo.kb'),
1725 'foo.kk' : props_for_path(wc_tree, '/trunk/foo.kk'),
1726 'foo.ko' : props_for_path(wc_tree, '/trunk/foo.ko'),
1727 'foo.kv' : props_for_path(wc_tree, '/trunk/foo.kv'),
1730 check_props(allprops, 'foo.default', 'Author Date Id Revision', 'native',
1731 None)
1732 check_props(allprops, 'foo.kkvl', 'Author Date Id Revision', 'native', None)
1733 check_props(allprops, 'foo.kkv', 'Author Date Id Revision', 'native', None)
1734 check_props(allprops, 'foo.kb', None, None, 'application/octet-stream')
1735 check_props(allprops, 'foo.kk', None, 'native', None)
1736 check_props(allprops, 'foo.ko', None, 'native', None)
1737 check_props(allprops, 'foo.kv', None, 'native', None)
1740 def ignore():
1741 "test setting of svn:ignore property"
1742 repos, wc, logs = ensure_conversion('cvsignore')
1743 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1744 topdir_props = props_for_path(wc_tree, 'trunk/proj')
1745 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
1747 if topdir_props['svn:ignore'] != \
1748 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1749 raise svntest.Failure
1751 if subdir_props['svn:ignore'] != \
1752 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1753 raise svntest.Failure
1756 def requires_cvs():
1757 "test that CVS can still do what RCS can't"
1758 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
1759 repos, wc, logs = ensure_conversion('requires-cvs', None, None, "--use-cvs")
1761 atsign_contents = file(os.path.join(wc, "trunk", "atsign-add")).read()
1762 cl_contents = file(os.path.join(wc, "trunk", "client_lock.idl")).read()
1764 if atsign_contents[-1:] == "@":
1765 raise svntest.Failure
1766 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
1767 raise svntest.Failure
1769 if not (logs[21].author == "William Lyon Phelps III" and
1770 logs[20].author == "j random"):
1771 raise svntest.Failure
1774 def questionable_symbols():
1775 "test that we can handle weird symbolic names"
1776 repos, wc, logs = ensure_conversion('questionable-symbols')
1777 # If the conversion succeeds, then we're okay. We could check the
1778 # actual branch paths, too, but the main thing is to know that the
1779 # conversion doesn't fail.
1781 def revision_reorder_bug():
1782 "reveal a bug that reorders file revisions"
1783 repos, wc, logs = ensure_conversion('revision-reorder-bug')
1784 # If the conversion succeeds, then we're okay. We could check the
1785 # actual revisions, too, but the main thing is to know that the
1786 # conversion doesn't fail.
1788 def exclude():
1789 "test that exclude really excludes everything"
1790 repos, wc, logs = ensure_conversion('main', None, None,
1791 '--exclude=.*')
1792 for log in logs.values():
1793 for item in log.changed_paths.keys():
1794 if item[:10] == '/branches/' or item[:6] == '/tags/':
1795 raise svntest.Failure
1798 def vendor_branch_delete_add():
1799 "add trunk file that was deleted on vendor branch"
1800 # This will error if the bug is present
1801 repos, wc, logs = ensure_conversion('vendor-branch-delete-add')
1803 def resync_pass2_ordering_bug():
1804 "reveal a bug that resyncs timestamps in pass2"
1805 repos, wc, logs = ensure_conversion('resync-pass2-ordering')
1806 # If the conversion succeeds, then we're okay. We could check the
1807 # actual revisions, too, but the main thing is to know that the
1808 # conversion doesn't fail.
1811 #----------------------------------------------------------------------
1813 ########################################################################
1814 # Run the tests
1816 # list all tests here, starting with None:
1817 test_list = [ None,
1818 show_usage,
1819 attr_exec,
1820 space_fname,
1821 two_quick,
1822 prune_with_care,
1823 interleaved_commits,
1824 simple_commits,
1825 simple_tags,
1826 simple_branch_commits,
1827 mixed_time_tag,
1828 mixed_time_branch_with_added_file,
1829 mixed_commit,
1830 split_time_branch,
1831 bogus_tag,
1832 overlapping_branch,
1833 phoenix_branch,
1834 ctrl_char_in_log,
1835 overdead,
1836 no_trunk_prune,
1837 double_delete,
1838 split_branch,
1839 resync_misgroups,
1840 tagged_branch_and_trunk,
1841 enroot_race,
1842 enroot_race_obo,
1843 branch_delete_first,
1844 nonascii_filenames,
1845 vendor_branch_sameness,
1846 default_branches,
1847 compose_tag_three_sources,
1848 pass5_when_to_fill,
1849 peer_path_pruning,
1850 empty_trunk,
1851 no_spurious_svn_commits,
1852 invalid_closings_on_trunk,
1853 individual_passes,
1854 resync_bug,
1855 branch_from_default_branch,
1856 file_in_attic_too,
1857 symbolic_name_filling_guide,
1858 eol_mime,
1859 keywords,
1860 ignore,
1861 requires_cvs,
1862 questionable_symbols,
1863 XFail(revision_reorder_bug),
1864 exclude,
1865 vendor_branch_delete_add,
1866 XFail(resync_pass2_ordering_bug),
1869 if __name__ == '__main__':
1871 # The Subversion test suite code assumes it's being invoked from
1872 # within a working copy of the Subversion sources, and tries to use
1873 # the binaries in that tree. Since the cvs2svn tree never contains
1874 # a Subversion build, we just use the system's installed binaries.
1875 svntest.main.svn_binary = 'svn'
1876 svntest.main.svnlook_binary = 'svnlook'
1877 svntest.main.svnadmin_binary = 'svnadmin'
1878 svntest.main.svnversion_binary = 'svnversion'
1880 svntest.main.run_tests(test_list)
1881 # NOTREACHED
1884 ### End of file.