Fix handling of "branch ;" in RCS file.
[cvs2svn.git] / run-tests.py
blobe0648c37acd29465c9bc7150c5d5a18ef35b09f3
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 for arg in args:
354 # Replace some characters that Win32 isn't happy about having in a
355 # filename (which was causing the eol_mime test to fail).
356 sanitized_arg = arg.replace("--", "-").replace("=", "-").replace("/", "+")
357 sanitized_arg = sanitized_arg.replace(":",".").replace("\\","+")
358 conv_id = conv_id + sanitized_arg
360 if passbypass:
361 conv_id = conv_id + '-passbypass'
363 if not already_converted.has_key(conv_id):
364 try:
365 if not os.path.isdir(tmp_dir):
366 os.mkdir(tmp_dir)
368 cvsrepos = os.path.abspath(os.path.join(test_data_dir,
369 '%s-cvsrepos' % name))
371 saved_wd = os.getcwd()
372 try:
373 os.chdir(tmp_dir)
375 svnrepos = '%s-svnrepos' % conv_id
376 wc = '%s-wc' % conv_id
378 # Clean up from any previous invocations of this script.
379 erase(svnrepos)
380 erase(wc)
382 try:
383 args.extend( [ '--bdb-txn-nosync', '-s', svnrepos, cvsrepos ] )
384 if passbypass:
385 for p in range(1, 9):
386 apply(run_cvs2svn, [ error_re, '-p', str(p) ] + args)
387 else:
388 ret = apply(run_cvs2svn, [ error_re ] + args)
389 except RunProgramException:
390 raise svntest.Failure
391 except MissingErrorException:
392 print "Test failed because no error matched '%s'" % error_re
393 raise svntest.Failure
395 if not os.path.isdir(svnrepos):
396 print "Repository not created: '%s'" \
397 % os.path.join(os.getcwd(), svnrepos)
398 raise svntest.Failure
400 run_svn('co', repos_to_url(svnrepos), wc)
401 log_dict = parse_log(svnrepos)
402 finally:
403 os.chdir(saved_wd)
405 # This name is done for the rest of this session.
406 already_converted[conv_id] = (os.path.join('tmp', svnrepos),
407 os.path.join('tmp', wc), log_dict)
408 except svntest.Failure:
409 # Remember the failure so that a future attempt to run this conversion
410 # does not bother to retry, but fails immediately.
411 already_converted[conv_id] = None
412 raise
414 if already_converted[conv_id] is None:
415 raise svntest.Failure
416 return already_converted[conv_id]
419 #----------------------------------------------------------------------
420 # Tests.
421 #----------------------------------------------------------------------
424 def show_usage():
425 "cvs2svn with no arguments shows usage"
426 out = run_cvs2svn(None)
427 if (len(out) > 2 and out[0].find('ERROR:') == 0
428 and out[1].find('DBM module')):
429 print 'cvs2svn cannot execute due to lack of proper DBM module.'
430 print 'Exiting without running any further tests.'
431 sys.exit(1)
432 if out[0].find('USAGE') < 0:
433 print 'Basic cvs2svn invocation failed.'
434 raise svntest.Failure
437 def attr_exec():
438 "detection of the executable flag"
439 if sys.platform == 'win32':
440 raise svntest.Skip
441 repos, wc, logs = ensure_conversion('main')
442 st = os.stat(os.path.join(wc, 'trunk', 'single-files', 'attr-exec'))
443 if not st[0] & stat.S_IXUSR:
444 raise svntest.Failure
447 def space_fname():
448 "conversion of filename with a space"
449 repos, wc, logs = ensure_conversion('main')
450 if not os.path.exists(os.path.join(wc, 'trunk', 'single-files',
451 'space fname')):
452 raise svntest.Failure
455 def two_quick():
456 "two commits in quick succession"
457 repos, wc, logs = ensure_conversion('main')
458 logs2 = parse_log(os.path.join(repos, 'trunk', 'single-files', 'twoquick'))
459 if len(logs2) != 2:
460 raise svntest.Failure
463 def prune_with_care():
464 "prune, but never too much"
465 # Robert Pluim encountered this lovely one while converting the
466 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
467 # repository (see issue #1302). Step 4 is the doozy:
469 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
470 # revision 2: adds trunk/blah/NEWS
471 # revision 3: deletes trunk/blah/cookie
472 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
473 # revision 5: does nothing
475 # After fixing cvs2svn, the sequence (correctly) looks like this:
477 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
478 # revision 2: adds trunk/blah/NEWS
479 # revision 3: deletes trunk/blah/cookie
480 # revision 4: does nothing [because trunk/blah/cookie already deleted]
481 # revision 5: deletes blah
483 # The difference is in 4 and 5. In revision 4, it's not correct to
484 # prune blah/, because NEWS is still in there, so revision 4 does
485 # nothing now. But when we delete NEWS in 5, that should bubble up
486 # and prune blah/ instead.
488 # ### Note that empty revisions like 4 are probably going to become
489 # ### at least optional, if not banished entirely from cvs2svn's
490 # ### output. Hmmm, or they may stick around, with an extra
491 # ### revision property explaining what happened. Need to think
492 # ### about that. In some sense, it's a bug in Subversion itself,
493 # ### that such revisions don't show up in 'svn log' output.
495 # In the test below, 'trunk/full-prune/first' represents
496 # cookie, and 'trunk/full-prune/second' represents NEWS.
498 repos, wc, logs = ensure_conversion('main')
500 # Confirm that revision 4 removes '/trunk/full-prune/first',
501 # and that revision 6 removes '/trunk/full-prune'.
503 # Also confirm similar things about '/full-prune-reappear/...',
504 # which is similar, except that later on it reappears, restored
505 # from pruneland, because a file gets added to it.
507 # And finally, a similar thing for '/partial-prune/...', except that
508 # in its case, a permanent file on the top level prevents the
509 # pruning from going farther than the subdirectory containing first
510 # and second.
512 rev = 11
513 for path in ('/trunk/full-prune/first',
514 '/trunk/full-prune-reappear/sub/first',
515 '/trunk/partial-prune/sub/first'):
516 if not (logs[rev].changed_paths.get(path) == 'D'):
517 print "Revision %d failed to remove '%s'." % (rev, path)
518 raise svntest.Failure
520 rev = 13
521 for path in ('/trunk/full-prune',
522 '/trunk/full-prune-reappear',
523 '/trunk/partial-prune/sub'):
524 if not (logs[rev].changed_paths.get(path) == 'D'):
525 print "Revision %d failed to remove '%s'." % (rev, path)
526 raise svntest.Failure
528 rev = 47
529 for path in ('/trunk/full-prune-reappear',
530 '/trunk/full-prune-reappear/appears-later'):
531 if not (logs[rev].changed_paths.get(path) == 'A'):
532 print "Revision %d failed to create path '%s'." % (rev, path)
533 raise svntest.Failure
536 def interleaved_commits():
537 "two interleaved trunk commits, different log msgs"
538 # See test-data/main-cvsrepos/proj/README.
539 repos, wc, logs = ensure_conversion('main')
541 # The initial import.
542 rev = 37
543 for path in ('/trunk/interleaved',
544 '/trunk/interleaved/1',
545 '/trunk/interleaved/2',
546 '/trunk/interleaved/3',
547 '/trunk/interleaved/4',
548 '/trunk/interleaved/5',
549 '/trunk/interleaved/a',
550 '/trunk/interleaved/b',
551 '/trunk/interleaved/c',
552 '/trunk/interleaved/d',
553 '/trunk/interleaved/e',):
554 if not (logs[rev].changed_paths.get(path) == 'A'):
555 raise svntest.Failure
557 if logs[rev].msg.find('Initial revision') != 0:
558 raise svntest.Failure
560 # This PEP explains why we pass the 'logs' parameter to these two
561 # nested functions, instead of just inheriting it from the enclosing
562 # scope: http://www.python.org/peps/pep-0227.html
564 def check_letters(rev, logs):
565 'Return 1 if REV is the rev where only letters were committed, else None.'
566 for path in ('/trunk/interleaved/a',
567 '/trunk/interleaved/b',
568 '/trunk/interleaved/c',
569 '/trunk/interleaved/d',
570 '/trunk/interleaved/e',):
571 if not (logs[rev].changed_paths.get(path) == 'M'):
572 return None
573 if logs[rev].msg.find('Committing letters only.') != 0:
574 return None
575 return 1
577 def check_numbers(rev, logs):
578 'Return 1 if REV is the rev where only numbers were committed, else None.'
579 for path in ('/trunk/interleaved/1',
580 '/trunk/interleaved/2',
581 '/trunk/interleaved/3',
582 '/trunk/interleaved/4',
583 '/trunk/interleaved/5',):
584 if not (logs[rev].changed_paths.get(path) == 'M'):
585 return None
586 if logs[rev].msg.find('Committing numbers only.') != 0:
587 return None
588 return 1
590 # One of the commits was letters only, the other was numbers only.
591 # But they happened "simultaneously", so we don't assume anything
592 # about which commit appeared first, we just try both ways.
593 rev = rev + 3
594 if not ((check_letters(rev, logs) and check_numbers(rev + 1, logs))
595 or (check_numbers(rev, logs) and check_letters(rev + 1, logs))):
596 raise svntest.Failure
599 def simple_commits():
600 "simple trunk commits"
601 # See test-data/main-cvsrepos/proj/README.
602 repos, wc, logs = ensure_conversion('main')
604 # The initial import.
605 rev = 23
606 if not logs[rev].changed_paths == {
607 '/trunk/proj': 'A',
608 '/trunk/proj/default': 'A',
609 '/trunk/proj/sub1': 'A',
610 '/trunk/proj/sub1/default': 'A',
611 '/trunk/proj/sub1/subsubA': 'A',
612 '/trunk/proj/sub1/subsubA/default': 'A',
613 '/trunk/proj/sub1/subsubB': 'A',
614 '/trunk/proj/sub1/subsubB/default': 'A',
615 '/trunk/proj/sub2': 'A',
616 '/trunk/proj/sub2/default': 'A',
617 '/trunk/proj/sub2/subsubA': 'A',
618 '/trunk/proj/sub2/subsubA/default': 'A',
619 '/trunk/proj/sub3': 'A',
620 '/trunk/proj/sub3/default': 'A',
622 raise svntest.Failure
624 if logs[rev].msg.find('Initial revision') != 0:
625 raise svntest.Failure
627 # The first commit.
628 rev = 30
629 if not logs[rev].changed_paths == {
630 '/trunk/proj/sub1/subsubA/default': 'M',
631 '/trunk/proj/sub3/default': 'M',
633 raise svntest.Failure
635 if logs[rev].msg.find('First commit to proj, affecting two files.') != 0:
636 raise svntest.Failure
638 # The second commit.
639 rev = 31
640 if not logs[rev].changed_paths == {
641 '/trunk/proj/default': 'M',
642 '/trunk/proj/sub1/default': 'M',
643 '/trunk/proj/sub1/subsubA/default': 'M',
644 '/trunk/proj/sub1/subsubB/default': 'M',
645 '/trunk/proj/sub2/default': 'M',
646 '/trunk/proj/sub2/subsubA/default': 'M',
647 '/trunk/proj/sub3/default': 'M'
649 raise svntest.Failure
651 if logs[rev].msg.find('Second commit to proj, affecting all 7 files.') != 0:
652 raise svntest.Failure
655 def simple_tags():
656 "simple tags and branches with no commits"
657 # See test-data/main-cvsrepos/proj/README.
658 repos, wc, logs = ensure_conversion('main')
660 # Verify the copy source for the tags we are about to check
661 # No need to verify the copyfrom revision, as simple_commits did that
662 check_rev(logs, 24, sym_log_msg('vendorbranch'), {
663 '/branches/vendorbranch/proj (from /trunk/proj:23)': 'A',
666 fromstr = ' (from /branches/vendorbranch:25)'
668 # Tag on rev 1.1.1.1 of all files in proj
669 rev = find_tag_rev(logs, 'T_ALL_INITIAL_FILES')
670 check_rev(logs, rev, sym_log_msg('T_ALL_INITIAL_FILES',1), {
671 '/tags/T_ALL_INITIAL_FILES'+fromstr: 'A',
672 '/tags/T_ALL_INITIAL_FILES/single-files': 'D',
673 '/tags/T_ALL_INITIAL_FILES/partial-prune': 'D',
676 # The same, as a branch
677 check_rev(logs, 26, sym_log_msg('B_FROM_INITIALS'), {
678 '/branches/B_FROM_INITIALS'+fromstr: 'A',
679 '/branches/B_FROM_INITIALS/single-files': 'D',
680 '/branches/B_FROM_INITIALS/partial-prune': 'D',
683 # Tag on rev 1.1.1.1 of all files in proj, except one
684 rev = find_tag_rev(logs, 'T_ALL_INITIAL_FILES_BUT_ONE')
685 check_rev(logs, rev, sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), {
686 '/tags/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr: 'A',
687 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/single-files': 'D',
688 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune': 'D',
689 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB': 'D',
692 # The same, as a branch
693 check_rev(logs, 27, sym_log_msg('B_FROM_INITIALS_BUT_ONE'), {
694 '/branches/B_FROM_INITIALS_BUT_ONE'+fromstr: 'A',
695 '/branches/B_FROM_INITIALS_BUT_ONE/single-files': 'D',
696 '/branches/B_FROM_INITIALS_BUT_ONE/partial-prune': 'D',
697 '/branches/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB': 'D',
701 def simple_branch_commits():
702 "simple branch commits"
703 # See test-data/main-cvsrepos/proj/README.
704 repos, wc, logs = ensure_conversion('main')
706 rev = 35
707 if not logs[rev].changed_paths == {
708 '/branches/B_MIXED/proj/default': 'M',
709 '/branches/B_MIXED/proj/sub1/default': 'M',
710 '/branches/B_MIXED/proj/sub2/subsubA/default': 'M',
712 raise svntest.Failure
714 if logs[rev].msg.find('Modify three files, on branch B_MIXED.') != 0:
715 raise svntest.Failure
718 def mixed_time_tag():
719 "mixed-time tag"
720 # See test-data/main-cvsrepos/proj/README.
721 repos, wc, logs = ensure_conversion('main')
723 rev = find_tag_rev(logs, 'T_MIXED')
724 expected = {
725 '/tags/T_MIXED (from /trunk:31)': 'A',
726 '/tags/T_MIXED/partial-prune': 'D',
727 '/tags/T_MIXED/single-files': 'D',
728 '/tags/T_MIXED/proj/sub2/subsubA (from /trunk/proj/sub2/subsubA:23)': 'R',
729 '/tags/T_MIXED/proj/sub3 (from /trunk/proj/sub3:30)': 'R',
731 if rev == 16:
732 expected['/tags'] = 'A'
733 if not logs[rev].changed_paths == expected:
734 raise svntest.Failure
737 def mixed_time_branch_with_added_file():
738 "mixed-time branch, and a file added to the branch"
739 # See test-data/main-cvsrepos/proj/README.
740 repos, wc, logs = ensure_conversion('main')
742 # A branch from the same place as T_MIXED in the previous test,
743 # plus a file added directly to the branch
744 check_rev(logs, 32, sym_log_msg('B_MIXED'), {
745 '/branches/B_MIXED (from /trunk:31)': 'A',
746 '/branches/B_MIXED/partial-prune': 'D',
747 '/branches/B_MIXED/single-files': 'D',
748 '/branches/B_MIXED/proj/sub2/subsubA (from /trunk/proj/sub2/subsubA:23)':
749 'R',
750 '/branches/B_MIXED/proj/sub3 (from /trunk/proj/sub3:30)': 'R',
753 check_rev(logs, 34, 'Add a file on branch B_MIXED.', {
754 '/branches/B_MIXED/proj/sub2/branch_B_MIXED_only': 'A',
758 def mixed_commit():
759 "a commit affecting both trunk and a branch"
760 # See test-data/main-cvsrepos/proj/README.
761 repos, wc, logs = ensure_conversion('main')
763 check_rev(logs, 36, 'A single commit affecting one file on branch B_MIXED '
764 'and one on trunk.', {
765 '/trunk/proj/sub2/default': 'M',
766 '/branches/B_MIXED/proj/sub2/branch_B_MIXED_only': 'M',
770 def split_time_branch():
771 "branch some trunk files, and later branch the rest"
772 # See test-data/main-cvsrepos/proj/README.
773 repos, wc, logs = ensure_conversion('main')
775 rev = 42
776 # First change on the branch, creating it
777 check_rev(logs, rev, sym_log_msg('B_SPLIT'), {
778 '/branches/B_SPLIT (from /trunk:36)': 'A',
779 '/branches/B_SPLIT/partial-prune': 'D',
780 '/branches/B_SPLIT/single-files': 'D',
781 '/branches/B_SPLIT/proj/sub1/subsubB': 'D',
784 check_rev(logs, rev + 1, 'First change on branch B_SPLIT.', {
785 '/branches/B_SPLIT/proj/default': 'M',
786 '/branches/B_SPLIT/proj/sub1/default': 'M',
787 '/branches/B_SPLIT/proj/sub1/subsubA/default': 'M',
788 '/branches/B_SPLIT/proj/sub2/default': 'M',
789 '/branches/B_SPLIT/proj/sub2/subsubA/default': 'M',
792 # A trunk commit for the file which was not branched
793 check_rev(logs, rev + 2, 'A trunk change to sub1/subsubB/default. '
794 'This was committed about an', {
795 '/trunk/proj/sub1/subsubB/default': 'M',
798 # Add the file not already branched to the branch, with modification:w
799 check_rev(logs, rev + 3, sym_log_msg('B_SPLIT'), {
800 '/branches/B_SPLIT/proj/sub1/subsubB (from /trunk/proj/sub1/subsubB:44)': 'A',
803 check_rev(logs, rev + 4, 'This change affects sub3/default and '
804 'sub1/subsubB/default, on branch', {
805 '/branches/B_SPLIT/proj/sub1/subsubB/default': 'M',
806 '/branches/B_SPLIT/proj/sub3/default': 'M',
810 def bogus_tag():
811 "conversion of invalid symbolic names"
812 ret, ign, ign = ensure_conversion('bogus-tag')
815 def overlapping_branch():
816 "ignore a file with a branch with two names"
817 repos, wc, logs = ensure_conversion('overlapping-branch',
818 '.*cannot also have name \'vendorB\'')
819 nonlap_path = '/trunk/nonoverlapping-branch'
820 lap_path = '/trunk/overlapping-branch'
821 rev = 4
822 if not (logs[rev].changed_paths.get('/branches/vendorA (from /trunk:3)')
823 == 'A'):
824 raise svntest.Failure
825 # We don't know what order the first two commits would be in, since
826 # they have different log messages but the same timestamps. As only
827 # one of the files would be on the vendorB branch in the regression
828 # case being tested here, we allow for either order.
829 if ((logs[rev].changed_paths.get('/branches/vendorB (from /trunk:2)')
830 == 'A')
831 or (logs[rev].changed_paths.get('/branches/vendorB (from /trunk:3)')
832 == 'A')):
833 raise svntest.Failure
834 if not logs[rev + 1].changed_paths == {}:
835 raise svntest.Failure
836 if len(logs) != rev + 1:
837 raise svntest.Failure
840 def phoenix_branch():
841 "convert a branch file rooted in a 'dead' revision"
842 repos, wc, logs = ensure_conversion('phoenix')
843 check_rev(logs, 8, sym_log_msg('volsung_20010721'), {
844 '/branches/volsung_20010721 (from /trunk:7)': 'A',
845 '/branches/volsung_20010721/file.txt' : 'D'
847 check_rev(logs, 9, 'This file was supplied by Jack Moffitt', {
848 '/branches/volsung_20010721/phoenix': 'A' })
851 ###TODO: We check for 4 changed paths here to accomodate creating tags
852 ###and branches in rev 1, but that will change, so this will
853 ###eventually change back.
854 def ctrl_char_in_log():
855 "handle a control char in a log message"
856 # This was issue #1106.
857 rev = 2
858 repos, wc, logs = ensure_conversion('ctrl-char-in-log')
859 if not ((logs[rev].changed_paths.get('/trunk/ctrl-char-in-log') == 'A')
860 and (len(logs[rev].changed_paths) == 1)):
861 print "Revision 2 of 'ctrl-char-in-log,v' was not converted successfully."
862 raise svntest.Failure
863 if logs[rev].msg.find('\x04') < 0:
864 print "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong."
865 raise svntest.Failure
868 def overdead():
869 "handle tags rooted in a redeleted revision"
870 repos, wc, logs = ensure_conversion('overdead')
873 def no_trunk_prune():
874 "ensure that trunk doesn't get pruned"
875 repos, wc, logs = ensure_conversion('overdead')
876 for rev in logs.keys():
877 rev_logs = logs[rev]
878 for changed_path in rev_logs.changed_paths.keys():
879 if changed_path == '/trunk' \
880 and rev_logs.changed_paths[changed_path] == 'D':
881 raise svntest.Failure
884 def double_delete():
885 "file deleted twice, in the root of the repository"
886 # This really tests several things: how we handle a file that's
887 # removed (state 'dead') in two successive revisions; how we
888 # handle a file in the root of the repository (there were some
889 # bugs in cvs2svn's svn path construction for top-level files); and
890 # the --no-prune option.
891 repos, wc, logs = ensure_conversion('double-delete', None, None,
892 '--trunk-only' , '--no-prune')
894 path = '/trunk/twice-removed'
895 rev = 2
896 if not (logs[rev].changed_paths.get(path) == 'A'):
897 raise svntest.Failure
899 if logs[rev].msg.find('Initial revision') != 0:
900 raise svntest.Failure
902 if not (logs[rev + 1].changed_paths.get(path) == 'D'):
903 raise svntest.Failure
905 if logs[rev + 1].msg.find('Remove this file for the first time.') != 0:
906 raise svntest.Failure
908 if logs[rev + 1].changed_paths.has_key('/trunk'):
909 raise svntest.Failure
912 def split_branch():
913 "branch created from both trunk and another branch"
914 # See test-data/split-branch-cvsrepos/README.
916 # The conversion will fail if the bug is present, and
917 # ensure_conversion would raise svntest.Failure.
918 repos, wc, logs = ensure_conversion('split-branch')
921 def resync_misgroups():
922 "resyncing should not misorder commit groups"
923 # See test-data/resync-misgroups-cvsrepos/README.
925 # The conversion will fail if the bug is present, and
926 # ensure_conversion would raise svntest.Failure.
927 repos, wc, logs = ensure_conversion('resync-misgroups')
930 def tagged_branch_and_trunk():
931 "allow tags with mixed trunk and branch sources"
932 repos, wc, logs = ensure_conversion('tagged-branch-n-trunk')
933 a_path = os.path.join(wc, 'tags', 'some-tag', 'a.txt')
934 b_path = os.path.join(wc, 'tags', 'some-tag', 'b.txt')
935 if not (os.path.exists(a_path) and os.path.exists(b_path)):
936 raise svntest.Failure
937 if (open(a_path, 'r').read().find('1.24') == -1) \
938 or (open(b_path, 'r').read().find('1.5') == -1):
939 raise svntest.Failure
942 def enroot_race():
943 "never use the rev-in-progress as a copy source"
944 # See issue #1427 and r8544.
945 repos, wc, logs = ensure_conversion('enroot-race')
946 rev = 8
947 if not logs[rev].changed_paths == {
948 '/branches/mybranch (from /trunk:7)': 'A',
949 '/branches/mybranch/proj/a.txt': 'D',
950 '/branches/mybranch/proj/b.txt': 'D',
952 raise svntest.Failure
953 if not logs[rev + 1].changed_paths == {
954 '/branches/mybranch/proj/c.txt': 'M',
955 '/trunk/proj/a.txt': 'M',
956 '/trunk/proj/b.txt': 'M',
958 raise svntest.Failure
961 def enroot_race_obo():
962 "do use the last completed rev as a copy source"
963 repos, wc, logs = ensure_conversion('enroot-race-obo')
964 if not ((len(logs) == 3) and
965 (logs[3].changed_paths.get('/branches/BRANCH (from /trunk:2)') == 'A')):
966 raise svntest.Failure
969 def branch_delete_first():
970 "correctly handle deletion as initial branch action"
971 # See test-data/branch-delete-first-cvsrepos/README.
973 # The conversion will fail if the bug is present, and
974 # ensure_conversion would raise svntest.Failure.
975 repos, wc, logs = ensure_conversion('branch-delete-first')
977 # 'file' was deleted from branch-1 and branch-2, but not branch-3
978 if os.path.exists(os.path.join(wc, 'branches', 'branch-1', 'file')):
979 raise svntest.Failure
980 if os.path.exists(os.path.join(wc, 'branches', 'branch-2', 'file')):
981 raise svntest.Failure
982 if not os.path.exists(os.path.join(wc, 'branches', 'branch-3', 'file')):
983 raise svntest.Failure
986 def nonascii_filenames():
987 "non ascii files converted incorrectly"
988 # see issue #1255
990 # on a en_US.iso-8859-1 machine this test fails with
991 # svn: Can't recode ...
993 # as described in the issue
995 # on a en_US.UTF-8 machine this test fails with
996 # svn: Malformed XML ...
998 # which means at least it fails. Unfortunately it won't fail
999 # with the same error...
1001 # mangle current locale settings so we know we're not running
1002 # a UTF-8 locale (which does not exhibit this problem)
1003 current_locale = locale.getlocale()
1004 new_locale = 'en_US.ISO8859-1'
1005 locale_changed = None
1007 # From http://docs.python.org/lib/module-sys.html
1009 # getfilesystemencoding():
1011 # Return the name of the encoding used to convert Unicode filenames
1012 # into system file names, or None if the system default encoding is
1013 # used. The result value depends on the operating system:
1015 # - On Windows 9x, the encoding is ``mbcs''.
1016 # - On Mac OS X, the encoding is ``utf-8''.
1017 # - On Unix, the encoding is the user's preference according to the
1018 # result of nl_langinfo(CODESET), or None if the
1019 # nl_langinfo(CODESET) failed.
1020 # - On Windows NT+, file names are Unicode natively, so no conversion is performed.
1022 # So we're going to skip this test on Mac OS X for now.
1023 if sys.platform == "darwin":
1024 raise svntest.Skip
1026 try:
1027 # change locale to non-UTF-8 locale to generate latin1 names
1028 locale.setlocale(locale.LC_ALL, # this might be too broad?
1029 new_locale)
1030 locale_changed = 1
1031 except locale.Error:
1032 raise svntest.Skip
1034 try:
1035 testdata_path = os.path.abspath('test-data')
1036 srcrepos_path = os.path.join(testdata_path,'main-cvsrepos')
1037 dstrepos_path = os.path.join(testdata_path,'non-ascii-cvsrepos')
1038 if not os.path.exists(dstrepos_path):
1039 # create repos from existing main repos
1040 shutil.copytree(srcrepos_path, dstrepos_path)
1041 base_path = os.path.join(dstrepos_path, 'single-files')
1042 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1043 os.path.join(base_path, 'two\366uick,v'))
1044 new_path = os.path.join(dstrepos_path, 'single\366files')
1045 os.rename(base_path, new_path)
1047 # if ensure_conversion can generate a
1048 repos, wc, logs = ensure_conversion('non-ascii', None, None,
1049 '--encoding=latin1')
1050 finally:
1051 if locale_changed:
1052 locale.setlocale(locale.LC_ALL, current_locale)
1053 svntest.main.safe_rmtree(dstrepos_path)
1056 def vendor_branch_sameness():
1057 "avoid spurious changes for initial revs "
1058 repos, wc, logs = ensure_conversion('vendor-branch-sameness')
1060 # There are four files in the repository:
1062 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1063 # the same contents, the file's default branch is 1.1.1,
1064 # and both revisions are in state 'Exp'.
1066 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1067 # 1.1 (the addition of a line of text).
1069 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1071 # d.txt: This file was created by 'cvs add' instead of import, so
1072 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1073 # The timestamp on the add is exactly the same as for the
1074 # imports of the other files.
1076 # (Log messages for the same revisions are the same in all files.)
1078 # What we expect to see is everyone added in r1, then trunk/proj
1079 # copied in r2. In the copy, only a.txt should be left untouched;
1080 # b.txt should be 'M'odified, and (for different reasons) c.txt and
1081 # d.txt should be 'D'eleted.
1083 rev = 2
1084 ###TODO Convert to check_rev.
1085 if logs[rev].msg.find('Initial revision') != 0:
1086 raise svntest.Failure
1088 if not logs[rev].changed_paths == {
1089 '/trunk/proj' : 'A',
1090 '/trunk/proj/a.txt' : 'A',
1091 '/trunk/proj/b.txt' : 'A',
1092 '/trunk/proj/c.txt' : 'A',
1093 '/trunk/proj/d.txt' : 'A',
1095 raise svntest.Failure
1097 if logs[rev + 1].msg.find(sym_log_msg('vbranchA')) != 0:
1098 raise svntest.Failure
1100 if not logs[rev + 1].changed_paths == {
1101 '/branches/vbranchA (from /trunk:2)' : 'A',
1102 '/branches/vbranchA/proj/d.txt' : 'D',
1104 raise svntest.Failure
1106 if logs[rev + 2].msg.find('First vendor branch revision.') != 0:
1107 raise svntest.Failure
1109 if not logs[rev + 2].changed_paths == {
1110 '/branches/vbranchA/proj/b.txt' : 'M',
1111 '/branches/vbranchA/proj/c.txt' : 'D',
1113 raise svntest.Failure
1116 def default_branches():
1117 "handle default branches correctly "
1118 repos, wc, logs = ensure_conversion('default-branches')
1120 # There are seven files in the repository:
1122 # a.txt:
1123 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1124 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1125 # committed (thus losing the default branch "1.1.1"), then
1126 # 1.1.1.4 was imported. All vendor import release tags are
1127 # still present.
1129 # b.txt:
1130 # Like a.txt, but without rev 1.2.
1132 # c.txt:
1133 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1135 # d.txt:
1136 # Same as the previous two, but 1.1.1 branch is unlabeled.
1138 # e.txt:
1139 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1141 # deleted-on-vendor-branch.txt,v:
1142 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1144 # added-then-imported.txt,v:
1145 # Added with 'cvs add' to create 1.1, then imported with
1146 # completely different contents to create 1.1.1.1, therefore
1147 # never had a default branch.
1150 check_rev(logs, 18, sym_log_msg('vtag-4',1), {
1151 '/tags/vtag-4 (from /branches/vbranchA:16)' : 'A',
1152 '/tags/vtag-4/proj/d.txt '
1153 '(from /branches/unlabeled-1.1.1/proj/d.txt:16)' : 'A',
1156 check_rev(logs, 6, sym_log_msg('vtag-1',1), {
1157 '/tags/vtag-1 (from /branches/vbranchA:5)' : 'A',
1158 '/tags/vtag-1/proj/d.txt '
1159 '(from /branches/unlabeled-1.1.1/proj/d.txt:5)' : 'A',
1162 check_rev(logs, 9, sym_log_msg('vtag-2',1), {
1163 '/tags/vtag-2 (from /branches/vbranchA:7)' : 'A',
1164 '/tags/vtag-2/proj/d.txt '
1165 '(from /branches/unlabeled-1.1.1/proj/d.txt:7)' : 'A',
1168 check_rev(logs, 12, sym_log_msg('vtag-3',1), {
1169 '/tags/vtag-3 (from /branches/vbranchA:10)' : 'A',
1170 '/tags/vtag-3/proj/d.txt '
1171 '(from /branches/unlabeled-1.1.1/proj/d.txt:10)' : 'A',
1172 '/tags/vtag-3/proj/e.txt '
1173 '(from /branches/unlabeled-1.1.1/proj/e.txt:10)' : 'A',
1176 check_rev(logs, 17, "This commit was generated by cvs2svn "
1177 "to compensate for changes in r16,", {
1178 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:16)' : 'R',
1179 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:16)' : 'R',
1180 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:16)' : 'R',
1181 '/trunk/proj/deleted-on-vendor-branch.txt '
1182 '(from /branches/vbranchA/proj/deleted-on-vendor-branch.txt:16)' : 'A',
1183 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:16)' : 'R',
1186 check_rev(logs, 16, "Import (vbranchA, vtag-4).", {
1187 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1188 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1189 '/branches/vbranchA/proj/a.txt' : 'M',
1190 '/branches/vbranchA/proj/added-then-imported.txt': 'M', # CHECK!!!
1191 '/branches/vbranchA/proj/b.txt' : 'M',
1192 '/branches/vbranchA/proj/c.txt' : 'M',
1193 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'A',
1196 check_rev(logs, 15, sym_log_msg('vbranchA'), {
1197 '/branches/vbranchA/proj/added-then-imported.txt '
1198 '(from /trunk/proj/added-then-imported.txt:14)' : 'A',
1201 check_rev(logs, 14, "Add a file to the working copy.", {
1202 '/trunk/proj/added-then-imported.txt' : 'A',
1205 check_rev(logs, 13, "First regular commit, to a.txt, on vtag-3.", {
1206 '/trunk/proj/a.txt' : 'M',
1209 check_rev(logs, 11, "This commit was generated by cvs2svn "
1210 "to compensate for changes in r10,", {
1211 '/trunk/proj/a.txt (from /branches/vbranchA/proj/a.txt:10)' : 'R',
1212 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:10)' : 'R',
1213 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:10)' : 'R',
1214 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:10)' : 'R',
1215 '/trunk/proj/deleted-on-vendor-branch.txt' : 'D',
1216 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:10)' : 'R',
1219 check_rev(logs, 10, "Import (vbranchA, vtag-3).", {
1220 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1221 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1222 '/branches/vbranchA/proj/a.txt' : 'M',
1223 '/branches/vbranchA/proj/b.txt' : 'M',
1224 '/branches/vbranchA/proj/c.txt' : 'M',
1225 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'D',
1228 check_rev(logs, 8, "This commit was generated by cvs2svn "
1229 "to compensate for changes in r7,", {
1230 '/trunk/proj/a.txt (from /branches/vbranchA/proj/a.txt:7)' : 'R',
1231 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:7)' : 'R',
1232 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:7)' : 'R',
1233 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:7)' : 'R',
1234 '/trunk/proj/deleted-on-vendor-branch.txt '
1235 '(from /branches/vbranchA/proj/deleted-on-vendor-branch.txt:7)' : 'R',
1236 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:7)' : 'R',
1239 check_rev(logs, 7, "Import (vbranchA, vtag-2).", {
1240 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1241 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1242 '/branches/vbranchA/proj/a.txt' : 'M',
1243 '/branches/vbranchA/proj/b.txt' : 'M',
1244 '/branches/vbranchA/proj/c.txt' : 'M',
1245 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'M',
1248 check_rev(logs, 5, "Import (vbranchA, vtag-1).", {})
1250 check_rev(logs, 4, sym_log_msg('vbranchA'), {
1251 '/branches/vbranchA (from /trunk:2)' : 'A',
1252 '/branches/vbranchA/proj/d.txt' : 'D',
1253 '/branches/vbranchA/proj/e.txt' : 'D',
1256 check_rev(logs, 3, sym_log_msg('unlabeled-1.1.1'), {
1257 '/branches/unlabeled-1.1.1 (from /trunk:2)' : 'A',
1258 '/branches/unlabeled-1.1.1/proj/a.txt' : 'D',
1259 '/branches/unlabeled-1.1.1/proj/b.txt' : 'D',
1260 '/branches/unlabeled-1.1.1/proj/c.txt' : 'D',
1261 '/branches/unlabeled-1.1.1/proj/deleted-on-vendor-branch.txt' : 'D',
1264 check_rev(logs, 2, "Initial revision", {
1265 '/trunk/proj' : 'A',
1266 '/trunk/proj/a.txt' : 'A',
1267 '/trunk/proj/b.txt' : 'A',
1268 '/trunk/proj/c.txt' : 'A',
1269 '/trunk/proj/d.txt' : 'A',
1270 '/trunk/proj/deleted-on-vendor-branch.txt' : 'A',
1271 '/trunk/proj/e.txt' : 'A',
1275 def compose_tag_three_sources():
1276 "compose a tag from three sources"
1277 repos, wc, logs = ensure_conversion('compose-tag-three-sources')
1279 check_rev(logs, 2, "Add on trunk", {
1280 '/trunk/tagged-on-trunk-1.2-a': 'A',
1281 '/trunk/tagged-on-trunk-1.2-b': 'A',
1282 '/trunk/tagged-on-trunk-1.1': 'A',
1283 '/trunk/tagged-on-b1': 'A',
1284 '/trunk/tagged-on-b2': 'A',
1287 check_rev(logs, 3, sym_log_msg('b1'), {
1288 '/branches/b1 (from /trunk:2)': 'A',
1291 check_rev(logs, 4, sym_log_msg('b2'), {
1292 '/branches/b2 (from /trunk:2)': 'A',
1295 check_rev(logs, 5, "Commit on branch b1", {
1296 '/branches/b1/tagged-on-trunk-1.2-a': 'M',
1297 '/branches/b1/tagged-on-trunk-1.2-b': 'M',
1298 '/branches/b1/tagged-on-trunk-1.1': 'M',
1299 '/branches/b1/tagged-on-b1': 'M',
1300 '/branches/b1/tagged-on-b2': 'M',
1303 check_rev(logs, 6, "Commit on branch b2", {
1304 '/branches/b2/tagged-on-trunk-1.2-a': 'M',
1305 '/branches/b2/tagged-on-trunk-1.2-b': 'M',
1306 '/branches/b2/tagged-on-trunk-1.1': 'M',
1307 '/branches/b2/tagged-on-b1': 'M',
1308 '/branches/b2/tagged-on-b2': 'M',
1311 check_rev(logs, 7, "Commit again on trunk", {
1312 '/trunk/tagged-on-trunk-1.2-a': 'M',
1313 '/trunk/tagged-on-trunk-1.2-b': 'M',
1314 '/trunk/tagged-on-trunk-1.1': 'M',
1315 '/trunk/tagged-on-b1': 'M',
1316 '/trunk/tagged-on-b2': 'M',
1319 check_rev(logs, 8, sym_log_msg('T',1), {
1320 '/tags/T (from /trunk:7)': 'A',
1321 '/tags/T/tagged-on-b2 (from /branches/b2/tagged-on-b2:7)': 'R',
1322 '/tags/T/tagged-on-trunk-1.1 (from /trunk/tagged-on-trunk-1.1:2)': 'R',
1323 '/tags/T/tagged-on-b1 (from /branches/b1/tagged-on-b1:7)': 'R',
1327 def pass5_when_to_fill():
1328 "reserve a svn revnum for a fill only when required"
1329 # The conversion will fail if the bug is present, and
1330 # ensure_conversion would raise svntest.Failure.
1331 repos, wc, logs = ensure_conversion('pass5-when-to-fill')
1334 def empty_trunk():
1335 "don't break when the trunk is empty"
1336 # The conversion will fail if the bug is present, and
1337 # ensure_conversion would raise svntest.Failure.
1338 repos, wc, logs = ensure_conversion('empty-trunk')
1340 def no_spurious_svn_commits():
1341 "ensure that we don't create any spurious commits"
1342 repos, wc, logs = ensure_conversion('phoenix')
1344 # Check spurious commit that could be created in CVSCommit._pre_commit
1345 # (When you add a file on a branch, CVS creates a trunk revision
1346 # in state 'dead'. If the log message of that commit is equal to
1347 # the one that CVS generates, we do not ever create a 'fill'
1348 # SVNCommit for it.)
1350 # and spurious commit that could be created in CVSCommit._commit
1351 # (When you add a file on a branch, CVS creates a trunk revision
1352 # in state 'dead'. If the log message of that commit is equal to
1353 # the one that CVS generates, we do not create a primary SVNCommit
1354 # for it.)
1355 check_rev(logs, 18, 'File added on branch xiphophorus', {
1356 '/branches/xiphophorus/added-on-branch.txt' : 'A',
1359 # Check to make sure that a commit *is* generated:
1360 # (When you add a file on a branch, CVS creates a trunk revision
1361 # in state 'dead'. If the log message of that commit is NOT equal
1362 # to the one that CVS generates, we create a primary SVNCommit to
1363 # serve as a home for the log message in question.
1364 check_rev(logs, 19, 'file added-on-branch2.txt was initially added on '
1365 + 'branch xiphophorus,\nand this log message was tweaked', {})
1367 # Check spurious commit that could be created in
1368 # CVSRevisionAggregator.attempt_to_commit_symbols
1369 # (We shouldn't consider a CVSRevision whose op is OP_DEAD as a
1370 # candidate for the LastSymbolicNameDatabase.
1371 check_rev(logs, 20, 'This file was also added on branch xiphophorus,', {
1372 '/branches/xiphophorus/added-on-branch2.txt' : 'A',
1376 def peer_path_pruning():
1377 "make sure that filling prunes paths correctly"
1378 repos, wc, logs = ensure_conversion('peer-path-pruning')
1379 check_rev(logs, 8, sym_log_msg('BRANCH'), {
1380 '/branches/BRANCH (from /trunk:6)' : 'A',
1381 '/branches/BRANCH/bar' : 'D',
1382 '/branches/BRANCH/foo (from /trunk/foo:7)' : 'R',
1385 def invalid_closings_on_trunk():
1386 "verify correct revs are copied to default branches"
1387 # The conversion will fail if the bug is present, and
1388 # ensure_conversion would raise svntest.Failure.
1389 repos, wc, logs = ensure_conversion('invalid-closings-on-trunk')
1392 def individual_passes():
1393 "run each pass individually"
1394 repos, wc, logs = ensure_conversion('main')
1395 repos2, wc2, logs2 = ensure_conversion('main', passbypass=1)
1397 if logs != logs2:
1398 raise svntest.Failure
1401 def resync_bug():
1402 "reveal a big bug in our resync algorithm"
1403 # This will fail if the bug is present
1404 repos, wc, logs = ensure_conversion('resync-bug')
1407 def branch_from_default_branch():
1408 "reveal a bug in our default branch detection code"
1409 repos, wc, logs = ensure_conversion('branch-from-default-branch')
1411 # This revision will be a default branch synchronization only
1412 # if cvs2svn is correctly determining default branch revisions.
1414 # The bug was that cvs2svn was treating revisions on branches off of
1415 # default branches as default branch revisions, resulting in
1416 # incorrectly regarding the branch off of the default branch as a
1417 # non-trunk default branch. Crystal clear? I thought so. See
1418 # issue #42 for more incoherent blathering.
1419 check_rev(logs, 6, "This commit was generated by cvs2svn", {
1420 '/trunk/proj/file.txt (from /branches/upstream/proj/file.txt:5)': 'R',
1423 def file_in_attic_too():
1424 "die if a file exists in and out of the attic"
1425 try:
1426 ensure_conversion('file-in-attic-too')
1427 raise MissingErrorException
1428 except svntest.Failure:
1429 pass
1431 def symbolic_name_filling_guide():
1432 "reveal a big bug in our SymbolicNameFillingGuide"
1433 # This will fail if the bug is present
1434 repos, wc, logs = ensure_conversion('symbolic-name-overfill')
1437 # Helpers for tests involving file contents and properties.
1439 class NodeTreeWalkException:
1440 "Exception class for node tree traversals."
1441 pass
1443 def node_for_path(node, path):
1444 "In the tree rooted under SVNTree NODE, return the node at PATH."
1445 if node.name != '__SVN_ROOT_NODE':
1446 raise NodeTreeWalkException
1447 path = path.strip('/')
1448 components = path.split('/')
1449 for component in components:
1450 node = svntest.tree.get_child(node, component)
1451 return node
1453 # Helper for tests involving properties.
1454 def props_for_path(node, path):
1455 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
1456 return node_for_path(node, path).props
1458 def eol_mime():
1459 "test eol settings and mime types together"
1460 ###TODO: It's a bit klugey to construct this path here. But so far
1461 ### there's only one test with a mime.types file. If we have more,
1462 ### we should abstract this into some helper, which would be located
1463 ### near ensure_conversion(). Note that it is a convention of this
1464 ### test suite for a mime.types file to be located in the top level
1465 ### of the CVS repository to which it applies.
1466 mime_path = os.path.abspath(os.path.join(test_data_dir,
1467 'eol-mime-cvsrepos',
1468 'mime.types'))
1470 def the_usual_suspects(wc_root):
1471 """Return a dictionary mapping files onto their prop dicts.
1472 The files are the six files of interest under WC_ROOT, but with
1473 the 'trunk/' prefix removed. Thus the returned dictionary looks
1474 like this:
1476 'foo.txt' ==> { property dictionary for '/trunk/foo.txt' }
1477 'foo.xml' ==> { property dictionary for '/trunk/foo.xml' }
1478 'foo.zip' ==> { property dictionary for '/trunk/foo.zip' }
1479 'foo.bin' ==> { property dictionary for '/trunk/foo.bin' }
1480 'foo.csv' ==> { property dictionary for '/trunk/foo.csv' }
1481 'foo.dbf' ==> { property dictionary for '/trunk/foo.dbf' }
1483 return {
1484 'foo.txt' : props_for_path(wc_root, 'trunk/foo.txt'),
1485 'foo.xml' : props_for_path(wc_root, 'trunk/foo.xml'),
1486 'foo.zip' : props_for_path(wc_root, 'trunk/foo.zip'),
1487 'foo.bin' : props_for_path(wc_root, 'trunk/foo.bin'),
1488 'foo.csv' : props_for_path(wc_root, 'trunk/foo.csv'),
1489 'foo.dbf' : props_for_path(wc_root, 'trunk/foo.dbf'),
1492 # We do four conversions. Each time, we pass --mime-types=FILE with
1493 # the same FILE, but vary --no-default-eol and --eol-from-mime-type.
1494 # Thus there's one conversion with neither flag, one with just the
1495 # former, one with just the latter, and one with both.
1497 # In two of the four conversions, we pass --cvs-revnums to make
1498 # certain that there are no bad interactions.
1500 ## Neither --no-default-eol nor --eol-from-mime-type. ##
1501 repos, wc, logs = ensure_conversion('eol-mime', None, None,
1502 '--mime-types=%s' % mime_path,
1503 '--cvs-revnums')
1504 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1505 allprops = the_usual_suspects(wc_tree)
1507 # foo.txt (no -kb, mime file says nothing)
1508 if allprops['foo.txt'].get('svn:eol-style') != 'native':
1509 raise svntest.Failure
1510 if allprops['foo.txt'].get('svn:mime-type') is not None:
1511 raise svntest.Failure
1512 if not allprops['foo.txt'].get('cvs2svn:cvs-rev') == '1.2':
1513 raise svntest.Failure
1515 # foo.xml (no -kb, mime file says text)
1516 if allprops['foo.xml'].get('svn:eol-style') != 'native':
1517 raise svntest.Failure
1518 if allprops['foo.xml'].get('svn:mime-type') != 'text/xml':
1519 raise svntest.Failure
1520 if not allprops['foo.xml'].get('cvs2svn:cvs-rev') == '1.2':
1521 raise svntest.Failure
1523 # foo.zip (no -kb, mime file says non-text)
1524 if allprops['foo.zip'].get('svn:eol-style') != 'native':
1525 raise svntest.Failure
1526 if allprops['foo.zip'].get('svn:mime-type') != 'application/zip':
1527 raise svntest.Failure
1528 if not allprops['foo.zip'].get('cvs2svn:cvs-rev') == '1.2':
1529 raise svntest.Failure
1531 # foo.bin (has -kb, mime file says nothing)
1532 if allprops['foo.bin'].get('svn:eol-style') is not None:
1533 raise svntest.Failure
1534 if allprops['foo.bin'].get('svn:mime-type') != 'application/octet-stream':
1535 raise svntest.Failure
1536 if not allprops['foo.bin'].get('cvs2svn:cvs-rev') == '1.2':
1537 raise svntest.Failure
1539 # foo.csv (has -kb, mime file says text)
1540 if allprops['foo.csv'].get('svn:eol-style') is not None:
1541 raise svntest.Failure
1542 if allprops['foo.csv'].get('svn:mime-type') != 'text/csv':
1543 raise svntest.Failure
1544 if not allprops['foo.csv'].get('cvs2svn:cvs-rev') == '1.2':
1545 raise svntest.Failure
1547 # foo.dbf (has -kb, mime file says non-text)
1548 if allprops['foo.dbf'].get('svn:eol-style') is not None:
1549 raise svntest.Failure
1550 if allprops['foo.dbf'].get('svn:mime-type') != 'application/what-is-dbf':
1551 raise svntest.Failure
1552 if not allprops['foo.dbf'].get('cvs2svn:cvs-rev') == '1.2':
1553 raise svntest.Failure
1555 ## Just --no-default-eol, not --eol-from-mime-type. ##
1556 repos, wc, logs = ensure_conversion('eol-mime', None, None,
1557 '--mime-types=%s' % mime_path,
1558 '--no-default-eol')
1559 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1560 allprops = the_usual_suspects(wc_tree)
1562 # foo.txt (no -kb, mime file says nothing)
1563 if allprops['foo.txt'].get('svn:eol-style') is not None:
1564 raise svntest.Failure
1565 if allprops['foo.txt'].get('svn:mime-type') is not None:
1566 raise svntest.Failure
1568 # foo.xml (no -kb, mime file says text)
1569 if allprops['foo.xml'].get('svn:eol-style') is not None:
1570 raise svntest.Failure
1571 if allprops['foo.xml'].get('svn:mime-type') != 'text/xml':
1572 raise svntest.Failure
1574 # foo.zip (no -kb, mime file says non-text)
1575 if allprops['foo.zip'].get('svn:eol-style') is not None:
1576 raise svntest.Failure
1577 if allprops['foo.zip'].get('svn:mime-type') != 'application/zip':
1578 raise svntest.Failure
1580 # foo.bin (has -kb, mime file says nothing)
1581 if allprops['foo.bin'].get('svn:eol-style') is not None:
1582 raise svntest.Failure
1583 if allprops['foo.bin'].get('svn:mime-type') != 'application/octet-stream':
1584 raise svntest.Failure
1586 # foo.csv (has -kb, mime file says text)
1587 if allprops['foo.csv'].get('svn:eol-style') is not None:
1588 raise svntest.Failure
1589 if allprops['foo.csv'].get('svn:mime-type') != 'text/csv':
1590 raise svntest.Failure
1592 # foo.dbf (has -kb, mime file says non-text)
1593 if allprops['foo.dbf'].get('svn:eol-style') is not None:
1594 raise svntest.Failure
1595 if allprops['foo.dbf'].get('svn:mime-type') != 'application/what-is-dbf':
1596 raise svntest.Failure
1598 ## Just --eol-from-mime-type, not --no-default-eol. ##
1599 repos, wc, logs = ensure_conversion('eol-mime', None, None,
1600 '--mime-types=%s' % mime_path,
1601 '--eol-from-mime-type',
1602 '--cvs-revnums')
1603 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1604 allprops = the_usual_suspects(wc_tree)
1606 # foo.txt (no -kb, mime file says nothing)
1607 if allprops['foo.txt'].get('svn:eol-style') != 'native':
1608 raise svntest.Failure
1609 if allprops['foo.txt'].get('svn:mime-type') is not None:
1610 raise svntest.Failure
1611 if not allprops['foo.txt'].get('cvs2svn:cvs-rev') == '1.2':
1612 raise svntest.Failure
1614 # foo.xml (no -kb, mime file says text)
1615 if allprops['foo.xml'].get('svn:eol-style') != 'native':
1616 raise svntest.Failure
1617 if allprops['foo.xml'].get('svn:mime-type') != 'text/xml':
1618 raise svntest.Failure
1619 if not allprops['foo.xml'].get('cvs2svn:cvs-rev') == '1.2':
1620 raise svntest.Failure
1622 # foo.zip (no -kb, mime file says non-text)
1623 if allprops['foo.zip'].get('svn:eol-style') != 'native':
1624 raise svntest.Failure
1625 if allprops['foo.zip'].get('svn:mime-type') != 'application/zip':
1626 raise svntest.Failure
1627 if not allprops['foo.zip'].get('cvs2svn:cvs-rev') == '1.2':
1628 raise svntest.Failure
1630 # foo.bin (has -kb, mime file says nothing)
1631 if allprops['foo.bin'].get('svn:eol-style') is not None:
1632 raise svntest.Failure
1633 if allprops['foo.bin'].get('svn:mime-type') != 'application/octet-stream':
1634 raise svntest.Failure
1635 if not allprops['foo.bin'].get('cvs2svn:cvs-rev') == '1.2':
1636 raise svntest.Failure
1638 # foo.csv (has -kb, mime file says text)
1639 if allprops['foo.csv'].get('svn:eol-style') is not None:
1640 raise svntest.Failure
1641 if allprops['foo.csv'].get('svn:mime-type') != 'text/csv':
1642 raise svntest.Failure
1643 if not allprops['foo.csv'].get('cvs2svn:cvs-rev') == '1.2':
1644 raise svntest.Failure
1646 # foo.dbf (has -kb, mime file says non-text)
1647 if allprops['foo.dbf'].get('svn:eol-style') is not None:
1648 raise svntest.Failure
1649 if allprops['foo.dbf'].get('svn:mime-type') != 'application/what-is-dbf':
1650 raise svntest.Failure
1651 if not allprops['foo.dbf'].get('cvs2svn:cvs-rev') == '1.2':
1652 raise svntest.Failure
1654 ## Both --no-default-eol and --eol-from-mime-type. ##
1655 repos, wc, logs = ensure_conversion('eol-mime', None, None,
1656 '--mime-types=%s' % mime_path,
1657 '--eol-from-mime-type',
1658 '--no-default-eol')
1659 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1660 allprops = the_usual_suspects(wc_tree)
1662 # foo.txt (no -kb, mime file says nothing)
1663 if allprops['foo.txt'].get('svn:eol-style') is not None:
1664 raise svntest.Failure
1665 if allprops['foo.txt'].get('svn:mime-type') is not None:
1666 raise svntest.Failure
1668 # foo.xml (no -kb, mime file says text)
1669 if allprops['foo.xml'].get('svn:eol-style') != 'native':
1670 raise svntest.Failure
1671 if allprops['foo.xml'].get('svn:mime-type') != 'text/xml':
1672 raise svntest.Failure
1674 # foo.zip (no -kb, mime file says non-text)
1675 if allprops['foo.zip'].get('svn:eol-style') is not None:
1676 raise svntest.Failure
1677 if allprops['foo.zip'].get('svn:mime-type') != 'application/zip':
1678 raise svntest.Failure
1680 # foo.bin (has -kb, mime file says nothing)
1681 if allprops['foo.bin'].get('svn:eol-style') is not None:
1682 raise svntest.Failure
1683 if allprops['foo.bin'].get('svn:mime-type') != 'application/octet-stream':
1684 raise svntest.Failure
1686 # foo.csv (has -kb, mime file says text)
1687 if allprops['foo.csv'].get('svn:eol-style') is not None:
1688 raise svntest.Failure
1689 if allprops['foo.csv'].get('svn:mime-type') != 'text/csv':
1690 raise svntest.Failure
1692 # foo.dbf (has -kb, mime file says non-text)
1693 if allprops['foo.dbf'].get('svn:eol-style') is not None:
1694 raise svntest.Failure
1695 if allprops['foo.dbf'].get('svn:mime-type') != 'application/what-is-dbf':
1696 raise svntest.Failure
1699 def keywords():
1700 "test setting of svn:keywords property among others"
1701 repos, wc, logs = ensure_conversion('keywords')
1702 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1703 allprops = {
1704 'foo.default' : props_for_path(wc_tree, '/trunk/foo.default'),
1705 'foo.kkvl' : props_for_path(wc_tree, '/trunk/foo.kkvl'),
1706 'foo.kkv' : props_for_path(wc_tree, '/trunk/foo.kkv'),
1707 'foo.kb' : props_for_path(wc_tree, '/trunk/foo.kb'),
1708 'foo.kk' : props_for_path(wc_tree, '/trunk/foo.kk'),
1709 'foo.ko' : props_for_path(wc_tree, '/trunk/foo.ko'),
1710 'foo.kv' : props_for_path(wc_tree, '/trunk/foo.kv'),
1713 if allprops['foo.default'].get('svn:keywords') != 'author date id revision':
1714 raise svntest.Failure
1715 if allprops['foo.default'].get('svn:eol-style') != 'native':
1716 raise svntest.Failure
1717 if allprops['foo.default'].get('svn:mime-type') is not None:
1718 raise svntest.Failure
1720 if allprops['foo.kkvl'].get('svn:keywords') != 'author date id revision':
1721 raise svntest.Failure
1722 if allprops['foo.kkvl'].get('svn:eol-style') != 'native':
1723 raise svntest.Failure
1724 if allprops['foo.kkvl'].get('svn:mime-type') is not None:
1725 raise svntest.Failure
1727 if allprops['foo.kkv'].get('svn:keywords') != 'author date id revision':
1728 raise svntest.Failure
1729 if allprops['foo.kkv'].get('svn:eol-style') != 'native':
1730 raise svntest.Failure
1731 if allprops['foo.kkv'].get('svn:mime-type') is not None:
1732 raise svntest.Failure
1734 if allprops['foo.kb'].get('svn:keywords') is not None:
1735 raise svntest.Failure
1736 if allprops['foo.kb'].get('svn:eol-style') is not None:
1737 raise svntest.Failure
1738 if allprops['foo.kb'].get('svn:mime-type') != 'application/octet-stream':
1739 raise svntest.Failure
1741 if allprops['foo.kk'].get('svn:keywords') is not None:
1742 raise svntest.Failure
1743 if allprops['foo.kk'].get('svn:eol-style') != 'native':
1744 raise svntest.Failure
1745 if allprops['foo.kk'].get('svn:mime-type') is not None:
1746 raise svntest.Failure
1748 if allprops['foo.ko'].get('svn:keywords') is not None:
1749 raise svntest.Failure
1750 if allprops['foo.ko'].get('svn:eol-style') != 'native':
1751 raise svntest.Failure
1752 if allprops['foo.default'].get('svn:mime-type') is not None:
1753 raise svntest.Failure
1755 if allprops['foo.kv'].get('svn:keywords') is not None:
1756 raise svntest.Failure
1757 if allprops['foo.kv'].get('svn:eol-style') != 'native':
1758 raise svntest.Failure
1759 if allprops['foo.kv'].get('svn:mime-type') is not None:
1760 raise svntest.Failure
1763 def ignore():
1764 "test setting of svn:ignore property"
1765 repos, wc, logs = ensure_conversion('cvsignore')
1766 wc_tree = svntest.tree.build_tree_from_wc(wc, 1)
1767 topdir_props = props_for_path(wc_tree, 'trunk/proj')
1768 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
1770 if topdir_props['svn:ignore'] != \
1771 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1772 raise svntest.Failure
1774 if subdir_props['svn:ignore'] != \
1775 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1776 raise svntest.Failure
1779 def requires_cvs():
1780 "test that CVS can still do what RCS can't"
1781 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
1782 repos, wc, logs = ensure_conversion('requires-cvs', None, None, "--use-cvs")
1784 atsign_contents = file(os.path.join(wc, "trunk", "atsign-add")).read()
1785 cl_contents = file(os.path.join(wc, "trunk", "client_lock.idl")).read()
1787 if atsign_contents[-1:] == "@":
1788 raise svntest.Failure
1789 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
1790 raise svntest.Failure
1792 if not (logs[21].author == "William Lyon Phelps III" and
1793 logs[20].author == "j random"):
1794 raise svntest.Failure
1797 def questionable_symbols():
1798 "test that we can handle weird symbolic names"
1799 repos, wc, logs = ensure_conversion('questionable-symbols')
1800 # If the conversion succeeds, then we're okay. We could check the
1801 # actual branch paths, too, but the main thing is to know that the
1802 # conversion doesn't fail.
1804 def revision_reorder_bug():
1805 "reveal a bug that reorders file revisions"
1806 repos, wc, logs = ensure_conversion('revision-reorder-bug')
1807 # If the conversion succeeds, then we're okay. We could check the
1808 # actual revisions, too, but the main thing is to know that the
1809 # conversion doesn't fail.
1811 def exclude():
1812 "test that exclude really excludes everything"
1813 repos, wc, logs = ensure_conversion('main', None, None,
1814 '--exclude=.*')
1815 for log in logs.values():
1816 for item in log.changed_paths.keys():
1817 if item[:10] == '/branches/' or item[:6] == '/tags/':
1818 raise svntest.Failure
1820 #----------------------------------------------------------------------
1822 ########################################################################
1823 # Run the tests
1825 # list all tests here, starting with None:
1826 test_list = [ None,
1827 show_usage,
1828 attr_exec,
1829 space_fname,
1830 two_quick,
1831 prune_with_care,
1832 interleaved_commits,
1833 simple_commits,
1834 simple_tags,
1835 simple_branch_commits,
1836 mixed_time_tag,
1837 mixed_time_branch_with_added_file,
1838 mixed_commit,
1839 split_time_branch,
1840 bogus_tag,
1841 overlapping_branch,
1842 phoenix_branch,
1843 ctrl_char_in_log,
1844 overdead,
1845 no_trunk_prune,
1846 double_delete,
1847 split_branch,
1848 resync_misgroups,
1849 tagged_branch_and_trunk,
1850 enroot_race,
1851 enroot_race_obo,
1852 branch_delete_first,
1853 nonascii_filenames,
1854 vendor_branch_sameness,
1855 default_branches,
1856 compose_tag_three_sources,
1857 pass5_when_to_fill,
1858 peer_path_pruning,
1859 empty_trunk,
1860 no_spurious_svn_commits,
1861 invalid_closings_on_trunk,
1862 individual_passes,
1863 resync_bug,
1864 branch_from_default_branch,
1865 file_in_attic_too,
1866 symbolic_name_filling_guide,
1867 eol_mime,
1868 keywords,
1869 ignore,
1870 requires_cvs,
1871 questionable_symbols,
1872 XFail(revision_reorder_bug),
1873 exclude,
1876 if __name__ == '__main__':
1878 # The Subversion test suite code assumes it's being invoked from
1879 # within a working copy of the Subversion sources, and tries to use
1880 # the binaries in that tree. Since the cvs2svn tree never contains
1881 # a Subversion build, we just use the system's installed binaries.
1882 svntest.main.svn_binary = 'svn'
1883 svntest.main.svnlook_binary = 'svnlook'
1884 svntest.main.svnadmin_binary = 'svnadmin'
1885 svntest.main.svnversion_binary = 'svnversion'
1887 svntest.main.run_tests(test_list)
1888 # NOTREACHED
1891 ### End of file.