New testcase, for bug discovered thanks to reproduction data from Vladimir Prus.
[cvs2svn.git] / run-tests.py
blob7e57a5282d0a466ddb51f94c24436480c188417e
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 tools/cvs2svn/. Make sure we're there.
31 if not (os.path.exists('cvs2svn.py') and os.path.exists('test-data')):
32 sys.stderr.write("error: I need to be run in 'tools/cvs2svn/' "
33 "in the Subversion tree.\n")
34 sys.exit(1)
36 # Find the Subversion test framework.
37 sys.path += [os.path.abspath('../../subversion/tests/clients/cmdline')]
38 import svntest
40 # Abbreviations
41 Skip = svntest.testcase.Skip
42 XFail = svntest.testcase.XFail
43 Item = svntest.wc.StateItem
45 cvs2svn = os.path.abspath('cvs2svn.py')
47 # We use the installed svn and svnlook binaries, instead of using
48 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
49 # behavior -- or even existence -- of local builds shouldn't affect
50 # the cvs2svn test suite.
51 svn = 'svn'
52 svnlook = 'svnlook'
54 test_data_dir = 'test-data'
55 tmp_dir = 'tmp'
58 #----------------------------------------------------------------------
59 # Helpers.
60 #----------------------------------------------------------------------
63 class RunProgramException:
64 pass
66 class MissingErrorException:
67 pass
69 def run_program(program, error_re, *varargs):
70 """Run PROGRAM with VARARGS, return stdout as a list of lines.
71 If there is any stderr and ERROR_RE is None, raise
72 RunProgramException, and print the stderr lines if
73 svntest.main.verbose_mode is true.
75 If ERROR_RE is not None, it is a string regular expression that must
76 match some line of stderr. If it fails to match, raise
77 MissingErrorExpection."""
78 out, err = svntest.main.run_command(program, 1, 0, *varargs)
79 if err:
80 if error_re:
81 for line in err:
82 if re.match(error_re, line):
83 return out
84 raise MissingErrorException
85 else:
86 if svntest.main.verbose_mode:
87 print '\n%s said:\n' % program
88 for line in err:
89 print ' ' + line,
90 print
91 raise RunProgramException
92 return out
95 def run_cvs2svn(error_re, *varargs):
96 """Run cvs2svn with VARARGS, return stdout as a list of lines.
97 If there is any stderr and ERROR_RE is None, raise
98 RunProgramException, and print the stderr lines if
99 svntest.main.verbose_mode is true.
101 If ERROR_RE is not None, it is a string regular expression that must
102 match some line of stderr. If it fails to match, raise
103 MissingErrorException."""
104 if sys.platform == "win32":
105 # For an unknown reason, without this special case, the cmd.exe process
106 # invoked by os.system('sort ...') in cvs2svn.py receives invalid stdio
107 # handles. Therefore, the redirection of the output to the .s-revs file
108 # fails.
109 return run_program("python", error_re, cvs2svn, *varargs)
110 else:
111 return run_program(cvs2svn, error_re, *varargs)
114 def run_svn(*varargs):
115 """Run svn with VARARGS; return stdout as a list of lines.
116 If there is any stderr, raise RunProgramException, and print the
117 stderr lines if svntest.main.verbose_mode is true."""
118 return run_program(svn, None, *varargs)
121 def repos_to_url(path_to_svn_repos):
122 """This does what you think it does."""
123 rpath = os.path.abspath(path_to_svn_repos)
124 if rpath[0] != '/':
125 rpath = '/' + rpath
126 return 'file://%s' % string.replace(rpath, os.sep, '/')
128 if hasattr(time, 'strptime'):
129 def svn_strptime(timestr):
130 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
131 else:
132 # This is for Python earlier than 2.3 on Windows
133 _re_rev_date = re.compile(r'(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)')
134 def svn_strptime(timestr):
135 matches = _re_rev_date.match(timestr).groups()
136 return tuple(map(int, matches)) + (0, 1, -1)
138 class Log:
139 def __init__(self, revision, author, date):
140 self.revision = revision
141 self.author = author
143 # Internally, we represent the date as seconds since epoch (UTC).
144 # Since standard subversion log output shows dates in localtime
146 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
148 # and time.mktime() converts from localtime, it all works out very
149 # happily.
150 self.date = time.mktime(svn_strptime(date[0:19]))
152 # The changed paths will be accumulated later, as log data is read.
153 # Keys here are paths such as '/trunk/foo/bar', values are letter
154 # codes such as 'M', 'A', and 'D'.
155 self.changed_paths = { }
157 # The msg will be accumulated later, as log data is read.
158 self.msg = ''
161 def parse_log(svn_repos):
162 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS."""
164 class LineFeeder:
165 'Make a list of lines behave like an open file handle.'
166 def __init__(self, lines):
167 self.lines = lines
168 def readline(self):
169 if len(self.lines) > 0:
170 return self.lines.pop(0)
171 else:
172 return None
174 def absorb_changed_paths(out, log):
175 'Read changed paths from OUT into Log item LOG, until no more.'
176 while 1:
177 line = out.readline()
178 if len(line) == 1: return
179 line = line[:-1]
180 op_portion = line[3:4]
181 path_portion = line[5:]
182 # # We could parse out history information, but currently we
183 # # just leave it in the path portion because that's how some
184 # # tests expect it.
186 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
187 # if m:
188 # path_portion = m.group(1)
189 log.changed_paths[path_portion] = op_portion
191 def absorb_message_body(out, num_lines, log):
192 'Read NUM_LINES of log message body from OUT into Log item LOG.'
193 i = 0
194 while i < num_lines:
195 line = out.readline()
196 log.msg += line
197 i += 1
199 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
200 '(?P<author>[^\|]+) \| '
201 '(?P<date>[^\|]+) '
202 '\| (?P<lines>[0-9]+) (line|lines)$')
204 log_separator = '-' * 72
206 logs = { }
208 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
210 while 1:
211 this_log = None
212 line = out.readline()
213 if not line: break
214 line = line[:-1]
216 if line.find(log_separator) == 0:
217 line = out.readline()
218 if not line: break
219 line = line[:-1]
220 m = log_start_re.match(line)
221 if m:
222 this_log = Log(int(m.group('rev')), m.group('author'), m.group('date'))
223 line = out.readline()
224 if not line.find('Changed paths:') == 0:
225 print 'unexpected log output (missing changed paths)'
226 print "Line: '%s'" % line
227 sys.exit(1)
228 absorb_changed_paths(out, this_log)
229 absorb_message_body(out, int(m.group('lines')), this_log)
230 logs[this_log.revision] = this_log
231 elif len(line) == 0:
232 break # We've reached the end of the log output.
233 else:
234 print 'unexpected log output (missing revision line)'
235 print "Line: '%s'" % line
236 sys.exit(1)
237 else:
238 print 'unexpected log output (missing log separator)'
239 print "Line: '%s'" % line
240 sys.exit(1)
242 return logs
245 def erase(path):
246 """Unconditionally remove PATH and its subtree, if any. PATH may be
247 non-existent, a file or symlink, or a directory."""
248 if os.path.isdir(path):
249 shutil.rmtree(path)
250 elif os.path.exists(path):
251 os.remove(path)
254 # List of already converted names; see the NAME argument to ensure_conversion.
256 # Keys are names, values are tuples: (svn_repos, svn_wc, log_dictionary).
257 # The log_dictionary comes from parse_log(svn_repos).
258 already_converted = { }
260 def ensure_conversion(name, error_re=None, trunk_only=None,
261 no_prune=None, encoding=None):
262 """Convert CVS repository NAME to Subversion, but only if it has not
263 been converted before by this invocation of this script. If it has
264 been converted before, do nothing.
266 If no error, return a tuple:
268 svn_repository_path, wc_path, log_dict
270 ...log_dict being the type of dictionary returned by parse_log().
272 If ERROR_RE is a string, it is a regular expression expected to
273 match some line of stderr printed by the conversion. If there is an
274 error and ERROR_RE is not set, then raise svntest.Failure.
276 If TRUNK_ONLY is set, then pass the --trunk-only option to cvs2svn.py
277 if converting NAME for the first time.
279 If NO_PRUNE is set, then pass the --no-prune option to cvs2svn.py
280 if converting NAME for the first time.
282 NAME is just one word. For example, 'main' would mean to convert
283 './test-data/main-cvsrepos', and after the conversion, the resulting
284 Subversion repository would be in './tmp/main-svnrepos', and a
285 checked out head working copy in './tmp/main-wc'."""
287 cvsrepos = os.path.abspath(os.path.join(test_data_dir, '%s-cvsrepos' % name))
289 if not already_converted.has_key(name):
291 if not os.path.isdir(tmp_dir):
292 os.mkdir(tmp_dir)
294 saved_wd = os.getcwd()
295 try:
296 os.chdir(tmp_dir)
298 svnrepos = '%s-svnrepos' % name
299 wc = '%s-wc' % name
301 # Clean up from any previous invocations of this script.
302 erase(svnrepos)
303 erase(wc)
305 try:
306 arg_list = [ '--bdb-txn-nosync', '--create', '-s', svnrepos, cvsrepos ]
308 if no_prune:
309 arg_list[:0] = [ '--no-prune' ]
311 if trunk_only:
312 arg_list[:0] = [ '--trunk-only' ]
314 if encoding:
315 arg_list[:0] = [ '--encoding=' + encoding ]
317 arg_list[:0] = [ error_re ]
319 ret = apply(run_cvs2svn, arg_list)
320 except RunProgramException:
321 raise svntest.Failure
322 except MissingErrorException:
323 print "Test failed because no error matched '%s'" % error_re
324 raise svntest.Failure
326 if not os.path.isdir(svnrepos):
327 print "Repository not created: '%s'" \
328 % os.path.join(os.getcwd(), svnrepos)
329 raise svntest.Failure
331 run_svn('co', repos_to_url(svnrepos), wc)
332 log_dict = parse_log(svnrepos)
333 finally:
334 os.chdir(saved_wd)
336 # This name is done for the rest of this session.
337 already_converted[name] = (os.path.join('tmp', svnrepos),
338 os.path.join('tmp', wc),
339 log_dict)
341 return already_converted[name]
344 #----------------------------------------------------------------------
345 # Tests.
346 #----------------------------------------------------------------------
349 def show_usage():
350 "cvs2svn with no arguments shows usage"
351 out = run_cvs2svn(None)
352 if out[0].find('USAGE') < 0:
353 print 'Basic cvs2svn invocation failed.'
354 raise svntest.Failure
357 def attr_exec():
358 "detection of the executable flag"
359 repos, wc, logs = ensure_conversion('main')
360 st = os.stat(os.path.join(wc, 'trunk', 'single-files', 'attr-exec'))
361 if not st[0] & stat.S_IXUSR:
362 raise svntest.Failure
365 def space_fname():
366 "conversion of filename with a space"
367 repos, wc, logs = ensure_conversion('main')
368 if not os.path.exists(os.path.join(wc, 'trunk', 'single-files',
369 'space fname')):
370 raise svntest.Failure
373 def two_quick():
374 "two commits in quick succession"
375 repos, wc, logs = ensure_conversion('main')
376 logs2 = parse_log(os.path.join(repos, 'trunk', 'single-files', 'twoquick'))
377 if len(logs2) != 2:
378 raise svntest.Failure
381 def prune_with_care():
382 "prune, but never too much"
383 # Robert Pluim encountered this lovely one while converting the
384 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
385 # repository (see issue #1302). Step 4 is the doozy:
387 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
388 # revision 2: adds trunk/blah/NEWS
389 # revision 3: deletes trunk/blah/cookie
390 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
391 # revision 5: does nothing
393 # After fixing cvs2svn, the sequence (correctly) looks like this:
395 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
396 # revision 2: adds trunk/blah/NEWS
397 # revision 3: deletes trunk/blah/cookie
398 # revision 4: does nothing [because trunk/blah/cookie already deleted]
399 # revision 5: deletes blah
401 # The difference is in 4 and 5. In revision 4, it's not correct to
402 # prune blah/, because NEWS is still in there, so revision 4 does
403 # nothing now. But when we delete NEWS in 5, that should bubble up
404 # and prune blah/ instead.
406 # ### Note that empty revisions like 4 are probably going to become
407 # ### at least optional, if not banished entirely from cvs2svn's
408 # ### output. Hmmm, or they may stick around, with an extra
409 # ### revision property explaining what happened. Need to think
410 # ### about that. In some sense, it's a bug in Subversion itself,
411 # ### that such revisions don't show up in 'svn log' output.
413 # In the test below, 'trunk/full-prune/first' represents
414 # cookie, and 'trunk/full-prune/second' represents NEWS.
416 repos, wc, logs = ensure_conversion('main')
418 # Confirm that revision 4 removes '/trunk/full-prune/first',
419 # and that revision 6 removes '/trunk/full-prune'.
421 # Also confirm similar things about '/full-prune-reappear/...',
422 # which is similar, except that later on it reappears, restored
423 # from pruneland, because a file gets added to it.
425 # And finally, a similar thing for '/partial-prune/...', except that
426 # in its case, a permanent file on the top level prevents the
427 # pruning from going farther than the subdirectory containing first
428 # and second.
430 rev = 7
431 for path in ('/trunk/full-prune/first',
432 '/trunk/full-prune-reappear/sub/first',
433 '/trunk/partial-prune/sub/first'):
434 if not (logs[rev].changed_paths.get(path) == 'D'):
435 print "Revision %d failed to remove '%s'." % (rev, path)
436 raise svntest.Failure
438 rev = 9
439 for path in ('/trunk/full-prune',
440 '/trunk/full-prune-reappear',
441 '/trunk/partial-prune/sub'):
442 if not (logs[rev].changed_paths.get(path) == 'D'):
443 print "Revision %d failed to remove '%s'." % (rev, path)
444 raise svntest.Failure
446 rev = 31
447 for path in ('/trunk/full-prune-reappear',
448 '/trunk/full-prune-reappear',
449 '/trunk/full-prune-reappear/appears-later'):
450 if not (logs[rev].changed_paths.get(path) == 'A'):
451 print "Revision %d failed to create path '%s'." % (rev, path)
452 raise svntest.Failure
455 def interleaved_commits():
456 "two interleaved trunk commits, different log msgs"
457 # See test-data/main-cvsrepos/proj/README.
458 repos, wc, logs = ensure_conversion('main')
460 # The initial import.
461 rev = 24
462 for path in ('/trunk/interleaved',
463 '/trunk/interleaved/1',
464 '/trunk/interleaved/2',
465 '/trunk/interleaved/3',
466 '/trunk/interleaved/4',
467 '/trunk/interleaved/5',
468 '/trunk/interleaved/a',
469 '/trunk/interleaved/b',
470 '/trunk/interleaved/c',
471 '/trunk/interleaved/d',
472 '/trunk/interleaved/e',):
473 if not (logs[rev].changed_paths.get(path) == 'A'):
474 raise svntest.Failure
476 if logs[rev].msg.find('Initial revision') != 0:
477 raise svntest.Failure
479 # This PEP explains why we pass the 'logs' parameter to these two
480 # nested functions, instead of just inheriting it from the enclosing
481 # scope: http://www.python.org/peps/pep-0227.html
483 def check_letters(rev, logs):
484 'Return 1 if REV is the rev where only letters were committed, else None.'
485 for path in ('/trunk/interleaved/a',
486 '/trunk/interleaved/b',
487 '/trunk/interleaved/c',
488 '/trunk/interleaved/d',
489 '/trunk/interleaved/e',):
490 if not (logs[rev].changed_paths.get(path) == 'M'):
491 return None
492 if logs[rev].msg.find('Committing letters only.') != 0:
493 return None
494 return 1
496 def check_numbers(rev, logs):
497 'Return 1 if REV is the rev where only numbers were committed, else None.'
498 for path in ('/trunk/interleaved/1',
499 '/trunk/interleaved/2',
500 '/trunk/interleaved/3',
501 '/trunk/interleaved/4',
502 '/trunk/interleaved/5',):
503 if not (logs[rev].changed_paths.get(path) == 'M'):
504 return None
505 if logs[rev].msg.find('Committing numbers only.') != 0:
506 return None
507 return 1
509 # One of the commits was letters only, the other was numbers only.
510 # But they happened "simultaneously", so we don't assume anything
511 # about which commit appeared first, we just try both ways.
512 rev = rev + 2
513 if not ((check_letters(rev, logs) and check_numbers(rev + 1, logs))
514 or (check_numbers(rev, logs) and check_letters(rev + 1, logs))):
515 raise svntest.Failure
518 def simple_commits():
519 "simple trunk commits"
520 # See test-data/main-cvsrepos/proj/README.
521 repos, wc, logs = ensure_conversion('main')
523 # The initial import.
524 rev = 16
525 if not logs[rev].changed_paths == {
526 '/trunk/proj': 'A',
527 '/trunk/proj/default': 'A',
528 '/trunk/proj/sub1': 'A',
529 '/trunk/proj/sub1/default': 'A',
530 '/trunk/proj/sub1/subsubA': 'A',
531 '/trunk/proj/sub1/subsubA/default': 'A',
532 '/trunk/proj/sub1/subsubB': 'A',
533 '/trunk/proj/sub1/subsubB/default': 'A',
534 '/trunk/proj/sub2': 'A',
535 '/trunk/proj/sub2/default': 'A',
536 '/trunk/proj/sub2/subsubA': 'A',
537 '/trunk/proj/sub2/subsubA/default': 'A',
538 '/trunk/proj/sub3': 'A',
539 '/trunk/proj/sub3/default': 'A',
541 raise svntest.Failure
543 if logs[rev].msg.find('Initial revision') != 0:
544 raise svntest.Failure
546 # The first commit.
547 rev = 18
548 if not logs[rev].changed_paths == {
549 '/trunk/proj/sub1/subsubA/default': 'M',
550 '/trunk/proj/sub3/default': 'M',
552 raise svntest.Failure
554 if logs[rev].msg.find('First commit to proj, affecting two files.') != 0:
555 raise svntest.Failure
557 # The second commit.
558 rev = 19
559 if not logs[rev].changed_paths == {
560 '/trunk/proj/default': 'M',
561 '/trunk/proj/sub1/default': 'M',
562 '/trunk/proj/sub1/subsubA/default': 'M',
563 '/trunk/proj/sub1/subsubB/default': 'M',
564 '/trunk/proj/sub2/default': 'M',
565 '/trunk/proj/sub2/subsubA/default': 'M',
566 '/trunk/proj/sub3/default': 'M'
568 raise svntest.Failure
570 if logs[rev].msg.find('Second commit to proj, affecting all 7 files.') != 0:
571 raise svntest.Failure
574 def simple_tags():
575 "simple tags and branches with no commits"
576 # See test-data/main-cvsrepos/proj/README.
577 repos, wc, logs = ensure_conversion('main')
579 # Verify the copy source for the tags we are about to check
580 # No need to verify r16, as simple_commits did that
581 rev = 17
582 if not logs[rev].changed_paths == {
583 '/branches/vendorbranch/proj (from /trunk/proj:16)': 'A',
585 raise svntest.Failure
587 if logs[rev].msg.find('Initial import.') != 0:
588 raise svntest.Failure
590 # Tag on rev 1.1.1.1 of all files in proj
591 rev = 36
592 if not logs[rev].changed_paths == {
593 '/tags/T_ALL_INITIAL_FILES (from /branches/vendorbranch:17)': 'A',
594 '/tags/T_ALL_INITIAL_FILES/single-files': 'D',
595 '/tags/T_ALL_INITIAL_FILES/partial-prune': 'D',
597 raise svntest.Failure
599 # The same, as a branch
600 rev = 33
601 if not logs[rev].changed_paths == {
602 '/branches/B_FROM_INITIALS (from /branches/vendorbranch:17)': 'A',
603 '/branches/B_FROM_INITIALS/single-files': 'D',
604 '/branches/B_FROM_INITIALS/partial-prune': 'D',
606 raise svntest.Failure
608 # Tag on rev 1.1.1.1 of all files in proj, except one
609 rev = 38
610 if not logs[rev].changed_paths == {
611 '/tags/T_ALL_INITIAL_FILES_BUT_ONE (from /branches/vendorbranch:17)': 'A',
612 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/single-files': 'D',
613 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune': 'D',
614 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB': 'D',
616 raise svntest.Failure
618 # The same, as a branch
619 rev = 34
620 if not logs[rev].changed_paths == {
621 '/branches/B_FROM_INITIALS_BUT_ONE (from /branches/vendorbranch:17)': 'A',
622 '/branches/B_FROM_INITIALS_BUT_ONE/single-files': 'D',
623 '/branches/B_FROM_INITIALS_BUT_ONE/partial-prune': 'D',
624 '/branches/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB': 'D',
626 raise svntest.Failure
628 def simple_branch_commits():
629 "simple branch commits"
630 # See test-data/main-cvsrepos/proj/README.
631 repos, wc, logs = ensure_conversion('main')
633 rev = 22
634 if not logs[rev].changed_paths == {
635 '/branches/B_MIXED/proj/default': 'M',
636 '/branches/B_MIXED/proj/sub1/default': 'M',
637 '/branches/B_MIXED/proj/sub2/subsubA/default': 'M',
639 raise svntest.Failure
641 if logs[rev].msg.find('Modify three files, on branch B_MIXED.') != 0:
642 raise svntest.Failure
645 def mixed_time_tag():
646 "mixed-time tag"
647 # See test-data/main-cvsrepos/proj/README.
648 repos, wc, logs = ensure_conversion('main')
650 rev = 35
651 if not logs[rev].changed_paths == {
652 '/tags': 'A',
653 '/tags/T_MIXED (from /trunk:19)': 'A',
654 '/tags/T_MIXED/partial-prune': 'D',
655 '/tags/T_MIXED/single-files': 'D',
656 '/tags/T_MIXED/proj/sub2/subsubA (from /trunk/proj/sub2/subsubA:16)': 'R',
657 '/tags/T_MIXED/proj/sub3 (from /trunk/proj/sub3:18)': 'R',
659 raise svntest.Failure
662 def mixed_time_branch_with_added_file():
663 "mixed-time branch, and a file added to the branch"
664 # See test-data/main-cvsrepos/proj/README.
665 repos, wc, logs = ensure_conversion('main')
667 # Empty revision, purely to store the log message of the dead 1.1 revision
668 # required by the RCS file format
669 rev = 20
670 if not logs[rev].changed_paths == { }:
671 raise svntest.Failure
673 if logs[rev].msg.find('file branch_B_MIXED_only was initially added on '
674 'branch B_MIXED.') != 0:
675 raise svntest.Failure
677 # A branch from the same place as T_MIXED in the previous test,
678 # plus a file added directly to the branch
679 rev = 21
680 if not logs[rev].changed_paths == {
681 '/branches/B_MIXED (from /trunk:20)': 'A',
682 '/branches/B_MIXED/partial-prune': 'D',
683 '/branches/B_MIXED/single-files': 'D',
684 '/branches/B_MIXED/proj/sub2/subsubA (from /trunk/proj/sub2/subsubA:16)':
685 'R',
686 '/branches/B_MIXED/proj/sub3 (from /trunk/proj/sub3:18)': 'R',
687 '/branches/B_MIXED/proj/sub2/branch_B_MIXED_only': 'A',
689 raise svntest.Failure
691 if logs[rev].msg.find('Add a file on branch B_MIXED.') != 0:
692 raise svntest.Failure
695 def mixed_commit():
696 "a commit affecting both trunk and a branch"
697 # See test-data/main-cvsrepos/proj/README.
698 repos, wc, logs = ensure_conversion('main')
700 rev = 23
701 if not logs[rev].changed_paths == {
702 '/trunk/proj/sub2/default': 'M',
703 '/branches/B_MIXED/proj/sub2/branch_B_MIXED_only': 'M',
705 raise svntest.Failure
707 if logs[rev].msg.find('A single commit affecting one file on branch B_MIXED '
708 'and one on trunk.') != 0:
709 raise svntest.Failure
712 def split_time_branch():
713 "branch some trunk files, and later branch the rest"
714 # See test-data/main-cvsrepos/proj/README.
715 repos, wc, logs = ensure_conversion('main')
717 # First change on the branch, creating it
718 rev = 28
719 if not logs[rev].changed_paths == {
720 '/branches/B_SPLIT (from /trunk:23)': 'A',
721 '/branches/B_SPLIT/partial-prune': 'D',
722 '/branches/B_SPLIT/single-files': 'D',
723 '/branches/B_SPLIT/proj/default': 'M',
724 '/branches/B_SPLIT/proj/sub1/default': 'M',
725 '/branches/B_SPLIT/proj/sub1/subsubA/default': 'M',
726 '/branches/B_SPLIT/proj/sub1/subsubB': 'D',
727 '/branches/B_SPLIT/proj/sub2/default': 'M',
728 '/branches/B_SPLIT/proj/sub2/subsubA/default': 'M',
730 raise svntest.Failure
732 if logs[rev].msg.find('First change on branch B_SPLIT.') != 0:
733 raise svntest.Failure
735 # A trunk commit for the file which was not branched
736 rev = 29
737 if not logs[rev].changed_paths == {
738 '/trunk/proj/sub1/subsubB/default': 'M',
740 raise svntest.Failure
742 if logs[rev].msg.find('A trunk change to sub1/subsubB/default. '
743 'This was committed about an') != 0:
744 raise svntest.Failure
746 # Add the file not already branched to the branch, with modification:w
747 rev = 30
748 if not logs[rev].changed_paths == {
749 '/branches/B_SPLIT/proj/sub1/subsubB (from /trunk/proj/sub1/subsubB:29)':
750 'A',
751 '/branches/B_SPLIT/proj/sub1/subsubB/default': 'M',
752 '/branches/B_SPLIT/proj/sub3/default': 'M',
754 raise svntest.Failure
756 if logs[rev].msg.find('This change affects sub3/default and '
757 'sub1/subsubB/default, on branch') != 0:
758 raise svntest.Failure
761 def bogus_tag():
762 "conversion of invalid symbolic names"
763 ret, ign, ign = ensure_conversion('bogus-tag')
766 def overlapping_branch():
767 "ignore a file with a branch with two names"
768 repos, wc, logs = ensure_conversion('overlapping-branch',
769 '.*cannot also have name \'vendorB\'')
770 nonlap_path = '/trunk/nonoverlapping-branch'
771 lap_path = '/trunk/overlapping-branch'
772 if not (logs[3].changed_paths.get('/branches/vendorA (from /trunk:2)')
773 == 'A'):
774 raise svntest.Failure
775 # We don't know what order the first two commits would be in, since
776 # they have different log messages but the same timestamps. As only
777 # one of the files would be on the vendorB branch in the regression
778 # case being tested here, we allow for either order.
779 if ((logs[3].changed_paths.get('/branches/vendorB (from /trunk:1)')
780 == 'A')
781 or (logs[3].changed_paths.get('/branches/vendorB (from /trunk:2)')
782 == 'A')):
783 raise svntest.Failure
784 if len(logs) > 3:
785 raise svntest.Failure
788 def tolerate_corruption():
789 "convert as much as can, despite a corrupt ,v file"
790 repos, wc, logs = ensure_conversion('corrupt', None, 1)
791 if not ((logs[1].changed_paths.get('/trunk') == 'A')
792 and (logs[1].changed_paths.get('/trunk/good') == 'A')
793 and (len(logs[1].changed_paths) == 2)):
794 print "Even the valid good,v was not converted."
795 raise svntest.Failure
798 def phoenix_branch():
799 "convert a branch file rooted in a 'dead' revision"
800 repos, wc, logs = ensure_conversion('phoenix')
801 chpaths = logs[4].changed_paths
802 if not ((chpaths.get('/branches/volsung_20010721 (from /trunk:3)') == 'A')
803 and (chpaths.get('/branches/volsung_20010721/phoenix') == 'A')
804 and (len(chpaths) == 2)):
805 print "Revision 4 not as expected."
806 raise svntest.Failure
809 def ctrl_char_in_log():
810 "handle a control char in a log message"
811 # This was issue #1106.
812 repos, wc, logs = ensure_conversion('ctrl-char-in-log')
813 if not ((logs[1].changed_paths.get('/trunk') == 'A')
814 and (logs[1].changed_paths.get('/trunk/ctrl-char-in-log') == 'A')
815 and (len(logs[1].changed_paths) == 2)):
816 print "Revision 1 of 'ctrl-char-in-log,v' was not converted successfully."
817 raise svntest.Failure
818 if logs[1].msg.find('\x04') < 0:
819 print "Log message of 'ctrl-char-in-log,v' (rev 1) is wrong."
820 raise svntest.Failure
823 def overdead():
824 "handle tags rooted in a redeleted revision"
825 repos, wc, logs = ensure_conversion('overdead')
828 def no_trunk_prune():
829 "ensure that trunk doesn't get pruned"
830 repos, wc, logs = ensure_conversion('overdead')
831 for rev in logs.keys():
832 rev_logs = logs[rev]
833 for changed_path in rev_logs.changed_paths.keys():
834 if changed_path == '/trunk' \
835 and rev_logs.changed_paths[changed_path] == 'D':
836 raise svntest.Failure
839 def double_delete():
840 "file deleted twice, in the root of the repository"
841 # This really tests several things: how we handle a file that's
842 # removed (state 'dead') in two successive revisions; how we
843 # handle a file in the root of the repository (there were some
844 # bugs in cvs2svn's svn path construction for top-level files); and
845 # the --no-prune option.
846 repos, wc, logs = ensure_conversion('double-delete', None, 1, 1)
848 path = '/trunk/twice-removed'
850 if not (logs[1].changed_paths.get(path) == 'A'):
851 raise svntest.Failure
853 if logs[1].msg.find('Initial revision') != 0:
854 raise svntest.Failure
856 if not (logs[2].changed_paths.get(path) == 'D'):
857 raise svntest.Failure
859 if logs[2].msg.find('Remove this file for the first time.') != 0:
860 raise svntest.Failure
862 if logs[2].changed_paths.has_key('/trunk'):
863 raise svntest.Failure
866 def split_branch():
867 "branch created from both trunk and another branch"
868 # See test-data/split-branch-cvsrepos/README.
870 # The conversion will fail if the bug is present, and
871 # ensure_conversion would raise svntest.Failure.
872 repos, wc, logs = ensure_conversion('split-branch')
875 def resync_misgroups():
876 "resyncing should not misorder commit groups"
877 # See test-data/resync-misgroups-cvsrepos/README.
879 # The conversion will fail if the bug is present, and
880 # ensure_conversion would raise svntest.Failure.
881 repos, wc, logs = ensure_conversion('resync-misgroups')
884 def tagged_branch_and_trunk():
885 "allow tags with mixed trunk and branch sources"
886 repos, wc, logs = ensure_conversion('tagged-branch-n-trunk')
887 a_path = os.path.join(wc, 'tags', 'some-tag', 'a.txt')
888 b_path = os.path.join(wc, 'tags', 'some-tag', 'b.txt')
889 if not (os.path.exists(a_path) and os.path.exists(b_path)):
890 raise svntest.Failure
891 if (open(a_path, 'r').read().find('1.24') == -1) \
892 or (open(b_path, 'r').read().find('1.5') == -1):
893 raise svntest.Failure
896 def enroot_race():
897 "never use the rev-in-progress as a copy source"
898 # See issue #1427 and r8544.
899 repos, wc, logs = ensure_conversion('enroot-race')
900 if not ((logs[6].changed_paths.get('/branches/mybranch (from /trunk:5)')
901 == 'A')
902 and (logs[6].changed_paths.get('/branches/mybranch/proj/c.txt')
903 == 'M')
904 and (logs[6].changed_paths.get('/trunk/proj/a.txt') == 'M')
905 and (logs[6].changed_paths.get('/trunk/proj/b.txt') == 'M')):
906 raise svntest.Failure
909 def enroot_race_obo():
910 "do use the last completed rev as a copy source"
911 repos, wc, logs = ensure_conversion('enroot-race-obo')
912 if not ((len(logs) == 2) and
913 (logs[2].changed_paths.get('/branches/BRANCH (from /trunk:1)') == 'A')):
914 raise svntest.Failure
917 def branch_delete_first():
918 "correctly handle deletion as initial branch action"
919 # See test-data/branch-delete-first-cvsrepos/README.
921 # The conversion will fail if the bug is present, and
922 # ensure_conversion would raise svntest.Failure.
923 repos, wc, logs = ensure_conversion('branch-delete-first')
925 # 'file' was deleted from branch-1 and branch-2, but not branch-3
926 if os.path.exists(os.path.join(wc, 'branches', 'branch-1', 'file')):
927 raise svntest.Failure
928 if os.path.exists(os.path.join(wc, 'branches', 'branch-2', 'file')):
929 raise svntest.Failure
930 if not os.path.exists(os.path.join(wc, 'branches', 'branch-3', 'file')):
931 raise svntest.Failure
934 def nonascii_filenames():
935 "non ascii files converted incorrectly"
936 # see issue #1255
938 # on a en_US.iso-8859-1 machine this test fails with
939 # svn: Can't recode ...
941 # as described in the issue
943 # on a en_US.UTF-8 machine this test fails with
944 # svn: Malformed XML ...
946 # which means at least it fails. Unfortunately it won't fail
947 # with the same error...
949 # mangle current locale settings so we know we're not running
950 # a UTF-8 locale (which does not exhibit this problem)
951 current_locale = locale.getlocale()
952 new_locale = 'en_US.ISO8859-1'
953 locale_changed = None
955 try:
956 # change locale to non-UTF-8 locale to generate latin1 names
957 locale.setlocale(locale.LC_ALL, # this might be too broad?
958 new_locale)
959 locale_changed = 1
960 except locale.Error:
961 raise svntest.Skip
963 try:
964 testdata_path = os.path.abspath('test-data')
965 srcrepos_path = os.path.join(testdata_path,'main-cvsrepos')
966 dstrepos_path = os.path.join(testdata_path,'non-ascii-cvsrepos')
967 if not os.path.exists(dstrepos_path):
968 # create repos from existing main repos
969 shutil.copytree(srcrepos_path, dstrepos_path)
970 base_path = os.path.join(dstrepos_path, 'single-files')
971 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
972 os.path.join(base_path, 'two\366uick,v'))
973 new_path = os.path.join(dstrepos_path, 'single\366files')
974 os.rename(base_path, new_path)
976 # if ensure_conversion can generate a
977 repos, wc, logs = ensure_conversion('non-ascii', encoding='latin1')
978 finally:
979 if locale_changed:
980 locale.setlocale(locale.LC_ALL, current_locale)
981 shutil.rmtree(dstrepos_path)
984 def vendor_branch_sameness():
985 "avoid spurious changes for initial revs "
986 repos, wc, logs = ensure_conversion('vendor-branch-sameness')
988 # There are four files in the repository:
990 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
991 # the same contents, the file's default branch is 1.1.1,
992 # and both revisions are in state 'Exp'.
994 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
995 # 1.1 (the addition of a line of text).
997 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
999 # d.txt: This file was created by 'cvs add' instead of import, so
1000 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1001 # The timestamp on the add is exactly the same as for the
1002 # imports of the other files.
1004 # (Log messages for the same revisions are the same in all files.)
1006 # What we expect to see is everyone added in r1, then trunk/proj
1007 # copied in r2. In the copy, only a.txt should be left untouched;
1008 # b.txt should be 'M'odified, and (for different reasons) c.txt and
1009 # d.txt should be 'D'eleted.
1011 if logs[1].msg.find('Initial revision') != 0:
1012 raise svntest.Failure
1014 if not logs[1].changed_paths == {
1015 '/trunk' : 'A',
1016 '/trunk/proj' : 'A',
1017 '/trunk/proj/a.txt' : 'A',
1018 '/trunk/proj/b.txt' : 'A',
1019 '/trunk/proj/c.txt' : 'A',
1020 '/trunk/proj/d.txt' : 'A',
1022 raise svntest.Failure
1024 if logs[2].msg.find('First vendor branch revision.') != 0:
1025 raise svntest.Failure
1027 if not logs[2].changed_paths == {
1028 '/branches' : 'A',
1029 '/branches/vbranchA (from /trunk:1)' : 'A',
1030 '/branches/vbranchA/proj/b.txt' : 'M',
1031 '/branches/vbranchA/proj/c.txt' : 'D',
1032 '/branches/vbranchA/proj/d.txt' : 'D',
1034 raise svntest.Failure
1037 def default_branches():
1038 "handle default branches correctly "
1039 repos, wc, logs = ensure_conversion('default-branches')
1041 # There are seven files in the repository:
1043 # a.txt:
1044 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1045 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1046 # committed (thus losing the default branch "1.1.1"), then
1047 # 1.1.1.4 was imported. All vendor import release tags are
1048 # still present.
1050 # b.txt:
1051 # Like a.txt, but without rev 1.2.
1053 # c.txt:
1054 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1056 # d.txt:
1057 # Same as the previous two, but 1.1.1 branch is unlabeled.
1059 # e.txt:
1060 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1062 # deleted-on-vendor-branch.txt,v:
1063 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1065 # added-then-imported.txt,v:
1066 # Added with 'cvs add' to create 1.1, then imported with
1067 # completely different contents to create 1.1.1.1, therefore
1068 # never had a default branch.
1071 if logs[14].msg.find("This commit was manufactured by cvs2svn "
1072 "to create tag 'vtag-4'.") != 0:
1073 raise svntest.Failure
1075 if not logs[14].changed_paths == {
1076 '/tags/vtag-4 (from /branches/vbranchA:9)' : 'A',
1077 '/tags/vtag-4/proj/d.txt '
1078 '(from /branches/unlabeled-1.1.1/proj/d.txt:9)' : 'A',
1080 raise svntest.Failure
1082 if logs[13].msg.find("This commit was manufactured by cvs2svn "
1083 "to create tag 'vtag-1'.") != 0:
1084 raise svntest.Failure
1086 if not logs[13].changed_paths == {
1087 '/tags/vtag-1 (from /branches/vbranchA:2)' : 'A',
1088 '/tags/vtag-1/proj/d.txt '
1089 '(from /branches/unlabeled-1.1.1/proj/d.txt:2)' : 'A',
1091 raise svntest.Failure
1093 if logs[12].msg.find("This commit was manufactured by cvs2svn "
1094 "to create tag 'vtag-2'.") != 0:
1095 raise svntest.Failure
1096 if not logs[12].changed_paths == {
1097 '/tags/vtag-2 (from /branches/vbranchA:3)' : 'A',
1098 '/tags/vtag-2/proj/d.txt '
1099 '(from /branches/unlabeled-1.1.1/proj/d.txt:3)' : 'A',
1101 raise svntest.Failure
1103 if logs[11].msg.find("This commit was manufactured by cvs2svn "
1104 "to create tag 'vtag-3'.") != 0:
1105 raise svntest.Failure
1106 if not logs[11].changed_paths == {
1107 '/tags' : 'A',
1108 '/tags/vtag-3 (from /branches/vbranchA:5)' : 'A',
1109 '/tags/vtag-3/proj/d.txt '
1110 '(from /branches/unlabeled-1.1.1/proj/d.txt:5)' : 'A',
1111 '/tags/vtag-3/proj/e.txt '
1112 '(from /branches/unlabeled-1.1.1/proj/e.txt:5)' : 'A',
1114 raise svntest.Failure
1116 if logs[10].msg.find("This commit was generated by cvs2svn "
1117 "to compensate for changes in r9,") != 0:
1118 raise svntest.Failure
1119 if not logs[10].changed_paths == {
1120 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:9)' : 'R',
1121 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:9)' : 'R',
1122 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:9)' : 'R',
1123 '/trunk/proj/deleted-on-vendor-branch.txt '
1124 '(from /branches/vbranchA/proj/deleted-on-vendor-branch.txt:9)' : 'A',
1125 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:9)' : 'R',
1127 raise svntest.Failure
1129 if logs[9].msg.find("Import (vbranchA, vtag-4).") != 0:
1130 raise svntest.Failure
1132 if not logs[9].changed_paths == {
1133 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1134 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1135 '/branches/vbranchA/proj/a.txt' : 'M',
1136 '/branches/vbranchA/proj/added-then-imported.txt '
1137 '(from /trunk/proj/added-then-imported.txt:7)' : 'A',
1138 '/branches/vbranchA/proj/b.txt' : 'M',
1139 '/branches/vbranchA/proj/c.txt' : 'M',
1140 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'A',
1142 raise svntest.Failure
1144 if logs[8].msg.find("First regular commit, to a.txt, on vtag-3.") != 0:
1145 raise svntest.Failure
1147 if not logs[8].changed_paths == {
1148 '/trunk/proj/a.txt' : 'M',
1150 raise svntest.Failure
1152 if logs[7].msg.find("Add a file to the working copy.") != 0:
1153 raise svntest.Failure
1155 if not logs[7].changed_paths == {
1156 '/trunk/proj/added-then-imported.txt' : 'A',
1158 raise svntest.Failure
1160 if logs[6].msg.find("This commit was generated by cvs2svn "
1161 "to compensate for changes in r5,") != 0:
1162 raise svntest.Failure
1163 if not logs[6].changed_paths == {
1164 '/trunk/proj/a.txt (from /branches/vbranchA/proj/a.txt:5)' : 'R',
1165 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:5)' : 'R',
1166 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:5)' : 'R',
1167 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:5)' : 'R',
1168 '/trunk/proj/deleted-on-vendor-branch.txt' : 'D',
1169 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:5)' : 'R',
1171 raise svntest.Failure
1173 if logs[5].msg.find("Import (vbranchA, vtag-3).") != 0:
1174 raise svntest.Failure
1176 if not logs[5].changed_paths == {
1177 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1178 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1179 '/branches/vbranchA/proj/a.txt' : 'M',
1180 '/branches/vbranchA/proj/b.txt' : 'M',
1181 '/branches/vbranchA/proj/c.txt' : 'M',
1182 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'D',
1184 raise svntest.Failure
1186 if logs[4].msg.find("This commit was generated by cvs2svn "
1187 "to compensate for changes in r3,") != 0:
1188 raise svntest.Failure
1189 if not logs[4].changed_paths == {
1190 '/trunk/proj/a.txt (from /branches/vbranchA/proj/a.txt:3)' : 'R',
1191 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:3)' : 'R',
1192 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:3)' : 'R',
1193 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:3)' : 'R',
1194 '/trunk/proj/deleted-on-vendor-branch.txt '
1195 '(from /branches/vbranchA/proj/deleted-on-vendor-branch.txt:3)' : 'R',
1196 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:3)' : 'R',
1198 raise svntest.Failure
1200 if logs[3].msg.find("Import (vbranchA, vtag-2).") != 0:
1201 raise svntest.Failure
1203 if not logs[3].changed_paths == {
1204 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1205 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1206 '/branches/vbranchA/proj/a.txt' : 'M',
1207 '/branches/vbranchA/proj/b.txt' : 'M',
1208 '/branches/vbranchA/proj/c.txt' : 'M',
1209 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'M',
1211 raise svntest.Failure
1213 if logs[2].msg.find("Import (vbranchA, vtag-1).") != 0:
1214 raise svntest.Failure
1216 if not logs[2].changed_paths == {
1217 '/branches' : 'A',
1218 '/branches/unlabeled-1.1.1 (from /trunk:1)' : 'A',
1219 '/branches/unlabeled-1.1.1/proj/a.txt' : 'D',
1220 '/branches/unlabeled-1.1.1/proj/b.txt' : 'D',
1221 '/branches/unlabeled-1.1.1/proj/c.txt' : 'D',
1222 '/branches/unlabeled-1.1.1/proj/deleted-on-vendor-branch.txt' : 'D',
1223 '/branches/vbranchA (from /trunk:1)' : 'A',
1224 '/branches/vbranchA/proj/d.txt' : 'D',
1225 '/branches/vbranchA/proj/e.txt' : 'D',
1227 raise svntest.Failure
1229 if logs[1].msg.find("Initial revision") != 0:
1230 raise svntest.Failure
1232 if not logs[1].changed_paths == {
1233 '/trunk' : 'A',
1234 '/trunk/proj' : 'A',
1235 '/trunk/proj/a.txt' : 'A',
1236 '/trunk/proj/b.txt' : 'A',
1237 '/trunk/proj/c.txt' : 'A',
1238 '/trunk/proj/d.txt' : 'A',
1239 '/trunk/proj/deleted-on-vendor-branch.txt' : 'A',
1240 '/trunk/proj/e.txt' : 'A',
1242 raise svntest.Failure
1245 def compose_tag_three_sources():
1246 "compose a tag from three sources"
1247 repos, wc, logs = ensure_conversion('compose-tag-three-sources')
1249 if not logs[1].changed_paths == { '/trunk': 'A', '/trunk/a': 'A',
1250 '/trunk/b': 'A', '/trunk/c': 'A' }: raise svntest.Failure
1252 if not logs[2].changed_paths == { '/branches': 'A',
1253 '/branches/b1 (from /trunk:1)': 'A', '/branches/b1/a': 'M',
1254 '/branches/b1/b': 'M', '/branches/b1/c': 'M',
1255 }: raise svntest.Failure
1257 if not logs[3].changed_paths == {
1258 '/branches/b2 (from /trunk:1)': 'A', '/branches/b2/a': 'M',
1259 '/branches/b2/b': 'M', '/branches/b2/c': 'M',
1260 }: raise svntest.Failure
1262 if not logs[4].changed_paths == { '/tags': 'A',
1263 '/tags/T (from /trunk:1)': 'A',
1264 '/tags/T/b (from /branches/b1/b:2)': 'R',
1265 '/tags/T/c (from /branches/b2/c:3)': 'R',
1266 }: raise svntest.Failure
1269 #----------------------------------------------------------------------
1271 ########################################################################
1272 # Run the tests
1274 # list all tests here, starting with None:
1275 test_list = [ None,
1276 show_usage,
1277 attr_exec,
1278 space_fname,
1279 two_quick,
1280 prune_with_care,
1281 interleaved_commits,
1282 simple_commits,
1283 simple_tags,
1284 simple_branch_commits,
1285 mixed_time_tag,
1286 mixed_time_branch_with_added_file,
1287 mixed_commit,
1288 split_time_branch,
1289 bogus_tag,
1290 overlapping_branch,
1291 tolerate_corruption,
1292 phoenix_branch,
1293 ctrl_char_in_log,
1294 overdead,
1295 no_trunk_prune,
1296 double_delete,
1297 split_branch,
1298 resync_misgroups,
1299 tagged_branch_and_trunk,
1300 enroot_race,
1301 enroot_race_obo,
1302 branch_delete_first,
1303 nonascii_filenames,
1304 vendor_branch_sameness,
1305 default_branches,
1306 XFail(compose_tag_three_sources),
1309 if __name__ == '__main__':
1310 svntest.main.run_tests(test_list)
1311 # NOTREACHED
1314 ### End of file.