blob: cd1efa405dcf62e795784e9ce07c695acc4c32ea [file] [log] [blame]
#!/usr/bin/env python
#
# run_tests.py: test suite for cvs2svn
#
# Subversion is a tool for revision control.
# See http://subversion.tigris.org for more information.
#
# ====================================================================
# Copyright (c) 2000-2003 CollabNet. All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://subversion.tigris.org/license-1.html.
# If newer versions of this license are posted there, you may use a
# newer version instead, at your option.
#
######################################################################
# General modules
import sys
import shutil
import stat
import string
import re
import os
import time
import os.path
# This script needs to run in tools/cvs2svn/. Make sure we're there.
if not (os.path.exists('cvs2svn.py') and os.path.exists('test-data')):
sys.stderr.write("error: I need to be run in 'tools/cvs2svn/' "
"in the Subversion tree.\n")
sys.exit(1)
# Find the Subversion test framework.
sys.path += [os.path.abspath('../../subversion/tests/clients/cmdline')]
import svntest
# Abbreviations
Skip = svntest.testcase.Skip
XFail = svntest.testcase.XFail
Item = svntest.wc.StateItem
cvs2svn = os.path.abspath('cvs2svn.py')
# We use the installed svn and svnlook binaries, instead of using
# svntest.main.run_svn() and svntest.main.run_svnlook(), because the
# behavior -- or even existence -- of local builds shouldn't affect
# the cvs2svn test suite.
svn = 'svn'
svnlook = 'svnlook'
test_data_dir = 'test-data'
tmp_dir = 'tmp'
#----------------------------------------------------------------------
# Helpers.
#----------------------------------------------------------------------
class RunProgramException:
pass
def run_program(program, error_re, *varargs):
"""Run PROGRAM with VARARGS, return stdout as a list of lines.
If there is any stderr and ERROR_RE is None, raise
RunProgramException, and print the stderr lines if
svntest.main.verbose_mode is true.
If ERROR_RE is not None, it is a string regular expression that must
match some line of the error output; if it matches, return None,
else return 1."""
out, err = svntest.main.run_command(program, 1, 0, *varargs)
if err:
if error_re:
for line in err:
if re.match(error_re, line):
return None
return 1 # We never matched, so return 1 for failure to match
else:
if svntest.main.verbose_mode:
print '\n%s said:\n' % program
for line in err:
print ' ' + line,
print
raise RunProgramException
elif error_re:
return 1
return out
def run_cvs2svn(error_re, *varargs):
"""Run cvs2svn with VARARGS, return stdout as a list of lines.
If there is any stderr and ERROR_RE is None, raise
RunProgramException, and print the stderr lines if
svntest.main.verbose_mode is true.
If ERROR_RE is not None, it is a string regular expression that must
match some line of the error output; if it matches, return None,
else return 1."""
return run_program(cvs2svn, error_re, *varargs)
def run_svn(*varargs):
"""Run svn with VARARGS; return stdout as a list of lines.
If there is any stderr, raise RunProgramException, and print the
stderr lines if svntest.main.verbose_mode is true."""
return run_program(svn, None, *varargs)
def repos_to_url(path_to_svn_repos):
"""This does what you think it does."""
return 'file://%s' % os.path.abspath(path_to_svn_repos)
class Log:
def __init__(self, revision, author, date):
self.revision = revision
self.author = author
# Internally, we represent the date as seconds since epoch (UTC).
# Since standard subversion log output shows dates in localtime
#
# "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
#
# and time.mktime() converts from localtime, it all works out very
# happily.
self.date = time.mktime(time.strptime(date[0:19], "%Y-%m-%d %H:%M:%S"))
# The changed paths will be accumulated later, as log data is read.
# Keys here are paths such as '/trunk/foo/bar', values are letter
# codes such as 'M', 'A', and 'D'.
self.changed_paths = { }
# The msg will be accumulated later, as log data is read.
self.msg = ''
def parse_log(svn_repos):
"""Return a dictionary of Logs, keyed on revision number, for SVN_REPOS."""
class LineFeeder:
'Make a list of lines behave like an open file handle.'
def __init__(self, lines):
self.lines = lines
def readline(self):
if len(self.lines) > 0:
return self.lines.pop(0)
else:
return None
def absorb_changed_paths(out, log):
'Read changed paths from OUT into Log item LOG, until no more.'
while 1:
line = out.readline()
if len(line) == 1: return
line = line[:-1]
log.changed_paths[line[5:]] = line[3:4]
def absorb_message_body(out, num_lines, log):
'Read NUM_LINES of log message body from OUT into Log item LOG.'
i = 0
while i < num_lines:
line = out.readline()
log.msg += line
i += 1
log_start_re = re.compile('^rev (?P<rev>[0-9]+): '
'(?P<author>[^\|]+) \| '
'(?P<date>[^\|]+) '
'\| (?P<lines>[0-9]+) (line|lines)$')
log_separator = '-' * 72
logs = { }
out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
while 1:
this_log = None
line = out.readline()
if not line: break
line = line[:-1]
if line.find(log_separator) == 0:
line = out.readline()
if not line: break
line = line[:-1]
m = log_start_re.match(line)
if m:
this_log = Log(int(m.group('rev')), m.group('author'), m.group('date'))
line = out.readline()
if not line.find('Changed paths:') == 0:
print 'unexpected log output (missing changed paths)'
print "Line: '%s'" % line
sys.exit(1)
absorb_changed_paths(out, this_log)
absorb_message_body(out, int(m.group('lines')), this_log)
logs[this_log.revision] = this_log
elif len(line) == 0:
break # We've reached the end of the log output.
else:
print 'unexpected log output (missing revision line)'
print "Line: '%s'" % line
sys.exit(1)
else:
print 'unexpected log output (missing log separator)'
print "Line: '%s'" % line
sys.exit(1)
return logs
def erase(path):
"""Unconditionally remove PATH and its subtree, if any. PATH may be
non-existent, a file or symlink, or a directory."""
if os.path.isdir(path):
shutil.rmtree(path)
elif os.path.exists(path):
os.remove(path)
# List of already converted names; see the NAME argument to ensure_conversion.
#
# Keys are names, values are tuples: (svn_repos, svn_wc, log_dictionary).
# The log_dictionary comes from parse_log(svn_repos).
already_converted = { }
def ensure_conversion(name, error_re=None, trunk_only=None, no_prune=None):
"""Convert CVS repository NAME to Subversion, but only if it has not
been converted before by this invocation of this script. If it has
been converted before, do nothing.
If no error, return a tuple:
svn_repository_path, wc_path, log_dict
...log_dict being the type of dictionary returned by parse_log().
If ERROR_RE is a string, it is a regular expression expected to
match some error line from a failed conversion, in which case return
the tuple (None, None, None) if it fails as expected, or (1, 1, 1)
if it fails to fail in the expected way.
If TRUNK_ONLY is set, then pass the --trunk-only option to cvs2svn.py
if converting NAME for the first time.
If NO_PRUNE is set, then pass the --no-prune option to cvs2svn.py
if converting NAME for the first time.
If there is an error, but ERROR_RE is not set, then just raise
svntest.Failure.
NAME is just one word. For example, 'main' would mean to convert
'./test-data/main-cvsrepos', and after the conversion, the resulting
Subversion repository would be in './tmp/main-svnrepos', and a
checked out head working copy in './tmp/main-wc'."""
cvsrepos = os.path.abspath(os.path.join(test_data_dir, '%s-cvsrepos' % name))
if not already_converted.has_key(name):
if not os.path.isdir(tmp_dir):
os.mkdir(tmp_dir)
saved_wd = os.getcwd()
try:
os.chdir(tmp_dir)
svnrepos = '%s-svnrepos' % name
wc = '%s-wc' % name
# Clean up from any previous invocations of this script.
erase(svnrepos)
erase(wc)
### I'd have preferred to assemble an arg list conditionally and
### then apply() it, or use extended call syntax. But that
### didn't work as expected; I don't know why, it's never been a
### problem before. But since the trunk_only arg will soon go
### away anyway, no point spending much effort on supporting it
### elegantly. Hence the four way conditional below.
try:
if no_prune:
if trunk_only:
ret = run_cvs2svn(error_re, '--trunk-only', '--no-prune',
'--create', '-s',
svnrepos, cvsrepos)
else:
ret = run_cvs2svn(error_re, '--no-prune', '--create', '-s',
svnrepos, cvsrepos)
else:
if trunk_only:
ret = run_cvs2svn(error_re, '--trunk-only', '--create', '-s',
svnrepos, cvsrepos)
else:
ret = run_cvs2svn(error_re, '--create', '-s', svnrepos, cvsrepos)
except RunProgramException:
raise svntest.Failure
# If we were expecting an error with error_re, then return Nones
# if we matched it, or 1s if not.
if error_re:
return ret, ret, ret
run_svn('co', repos_to_url(svnrepos), wc)
log_dict = parse_log(svnrepos)
finally:
os.chdir(saved_wd)
# This name is done for the rest of this session.
already_converted[name] = (os.path.join('tmp', svnrepos),
os.path.join('tmp', wc),
log_dict)
return already_converted[name]
#----------------------------------------------------------------------
# Tests.
#----------------------------------------------------------------------
def show_usage():
"cvs2svn with no arguments shows usage"
out = run_cvs2svn(None)
if out[0].find('USAGE') < 0:
print 'Basic cvs2svn invocation failed.'
raise svntest.Failure
def attr_exec():
"detection of the executable flag"
repos, wc, logs = ensure_conversion('main')
st = os.stat(os.path.join(wc, 'trunk', 'single-files', 'attr-exec'))
if not st[0] & stat.S_IXUSR:
raise svntest.Failure
def bogus_tag():
"fail on encountering an invalid symbolic name"
ret, ign, ign = ensure_conversion('bogus-tag',
'.*is not a valid tag or branch name')
if ret:
raise svntest.Failure
def overlapping_branch():
"fail early on encountering a branch with two names"
ret, ign, ign = ensure_conversion('overlapping-branch',
'.*already has name')
if ret:
raise svntest.Failure
def tolerate_corruption():
"convert as much as can, despite a corrupt ,v file"
repos, wc, logs = ensure_conversion('corrupt', None, 1)
if not ((logs[1].changed_paths.get('/trunk') == 'A')
and (logs[1].changed_paths.get('/trunk/good') == 'A')
and (len(logs[1].changed_paths) == 2)):
print "Even the valid good,v was not converted."
raise svntest.Failure
def space_fname():
"conversion of filename with a space"
repos, wc, logs = ensure_conversion('main')
if not os.path.exists(os.path.join(wc, 'trunk', 'single-files',
'space fname')):
raise svntest.Failure
def ctrl_char_in_log():
"handle a control char in a log message"
# This was issue #1106.
repos, wc, logs = ensure_conversion('ctrl-char-in-log')
if not ((logs[1].changed_paths.get('/trunk') == 'A')
and (logs[1].changed_paths.get('/trunk/ctrl-char-in-log') == 'A')
and (len(logs[1].changed_paths) == 2)):
print "Revision 1 of 'ctrl-char-in-log,v' was not converted successfully."
raise svntest.Failure
if logs[1].msg.find('\x04') < 0:
print "Log message of 'ctrl-char-in-log,v' (rev 1) is wrong."
raise svntest.Failure
def overdead():
"handle tags rooted in a redeleted revision"
repos, wc, logs = ensure_conversion('overdead')
def phoenix_branch():
"convert a branch file rooted in a 'dead' revision"
repos, wc, logs = ensure_conversion('phoenix')
if not ((logs[4].changed_paths.get('/branches/volsung_20010721 '
'(from /trunk:2)') == 'A')
and (logs[4].changed_paths.get('/branches/volsung_20010721/'
'phoenix') == 'M')
and (len(logs[4].changed_paths) == 2)):
print "Revision 4 not as expected."
raise svntest.Failure
def two_quick():
"two commits in quick succession"
repos, wc, logs = ensure_conversion('main')
out = run_svn('log', os.path.join(wc, 'trunk', 'single-files', 'twoquick'))
num_revisions = 0
for line in out:
if line.find("rev ") == 0:
num_revisions = num_revisions + 1
if num_revisions != 2:
raise svntest.Failure
def prune_with_care():
"prune, but never too much"
# Robert Pluim encountered this lovely one while converting the
# directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
# repository (see issue #1302). Step 4 is the doozy:
#
# revision 1: adds trunk/blah/, adds trunk/blah/cookie
# revision 2: adds trunk/blah/NEWS
# revision 3: deletes trunk/blah/cookie
# revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
# revision 5: does nothing
#
# After fixing cvs2svn, the sequence (correctly) looks like this:
#
# revision 1: adds trunk/blah/, adds trunk/blah/cookie
# revision 2: adds trunk/blah/NEWS
# revision 3: deletes trunk/blah/cookie
# revision 4: does nothing [because trunk/blah/cookie already deleted]
# revision 5: deletes blah
#
# The difference is in 4 and 5. In revision 4, it's not correct to
# prune blah/, because NEWS is still in there, so revision 4 does
# nothing now. But when we delete NEWS in 5, that should bubble up
# and prune blah/ instead.
#
# ### Note that empty revisions like 4 are probably going to become
# ### at least optional, if not banished entirely from cvs2svn's
# ### output. Hmmm, or they may stick around, with an extra
# ### revision property explaining what happened. Need to think
# ### about that. In some sense, it's a bug in Subversion itself,
# ### that such revisions don't show up in 'svn log' output.
#
# In the test below, 'trunk/full-prune/first' represents
# cookie, and 'trunk/full-prune/second' represents NEWS.
repos, wc, logs = ensure_conversion('main')
# Confirm that revision 4 removes '/trunk/full-prune/first',
# and that revision 6 removes '/trunk/full-prune'.
#
# Also confirm similar things about '/full-prune-reappear/...',
# which is similar, except that later on it reappears, restored
# from pruneland, because a file gets added to it.
#
# And finally, a similar thing for '/partial-prune/...', except that
# in its case, a permanent file on the top level prevents the
# pruning from going farther than the subdirectory containing first
# and second.
rev = 7
for path in ('/trunk/full-prune/first',
'/trunk/full-prune-reappear/sub/first',
'/trunk/partial-prune/sub/first'):
if not (logs[rev].changed_paths.get(path) == 'D'):
print "Revision %d failed to remove '%s'." % (rev, path)
raise svntest.Failure
rev = 9
for path in ('/trunk/full-prune',
'/trunk/full-prune-reappear',
'/trunk/partial-prune/sub'):
if not (logs[rev].changed_paths.get(path) == 'D'):
print "Revision %d failed to remove '%s'." % (rev, path)
raise svntest.Failure
rev = 30
for path in ('/trunk/full-prune-reappear',
'/trunk/full-prune-reappear',
'/trunk/full-prune-reappear/appears-later'):
if not (logs[rev].changed_paths.get(path) == 'A'):
print "Revision %d failed to create path '%s'." % (rev, path)
raise svntest.Failure
def double_delete():
"file deleted twice, in the root of the repository"
# This really tests several things: how we handle a file that's
# removed (state 'dead') in two successive revisions; how we
# handle a file in the root of the repository (there were some
# bugs in cvs2svn's svn path construction for top-level files); and
# the --no-prune option.
repos, wc, logs = ensure_conversion('double-delete', None, 1, 1)
path = '/trunk/twice-removed'
if not (logs[1].changed_paths.get(path) == 'A'):
raise svntest.Failure
if logs[1].msg.find('Initial revision') != 0:
raise svntest.Failure
if not (logs[2].changed_paths.get(path) == 'D'):
raise svntest.Failure
if logs[2].msg.find('Remove this file for the first time.') != 0:
raise svntest.Failure
if logs[2].changed_paths.has_key('/trunk'):
raise svntest.Failure
def simple_commits():
"simple trunk commits"
# See test-data/main-cvsrepos/proj/README.
repos, wc, logs = ensure_conversion('main')
# The initial import.
rev = 16
for path in ('/trunk/proj', '/trunk/proj/default', '/trunk/proj/sub1',
'/trunk/proj/sub1/default', '/trunk/proj/sub1/subsubA',
'/trunk/proj/sub1/subsubA/default', '/trunk/proj/sub1/subsubB',
'/trunk/proj/sub1/subsubB/default', '/trunk/proj/sub2',
'/trunk/proj/sub2/default', '/trunk/proj/sub2/subsubA',
'/trunk/proj/sub2/subsubA/default', '/trunk/proj/sub3',
'/trunk/proj/sub3/default'):
if not (logs[rev].changed_paths.get(path) == 'A'):
raise svntest.Failure
if logs[rev].msg.find('Initial revision') != 0:
raise svntest.Failure
# The first commit.
rev = 18
for path in ('/trunk/proj/sub1/subsubA/default', '/trunk/proj/sub3/default'):
if not (logs[rev].changed_paths.get(path) == 'M'):
raise svntest.Failure
if logs[rev].msg.find('First commit to proj, affecting two files.') != 0:
raise svntest.Failure
# The second commit.
rev = 19
for path in ('/trunk/proj/default', '/trunk/proj/sub1/default',
'/trunk/proj/sub1/subsubA/default',
'/trunk/proj/sub1/subsubB/default',
'/trunk/proj/sub2/default',
'/trunk/proj/sub2/subsubA/default',
'/trunk/proj/sub3/default'):
if not (logs[rev].changed_paths.get(path) == 'M'):
raise svntest.Failure
if logs[rev].msg.find('Second commit to proj, affecting all 7 files.') != 0:
raise svntest.Failure
def interleaved_commits():
"two interleaved trunk commits, different log msgs"
# See test-data/main-cvsrepos/proj/README.
repos, wc, logs = ensure_conversion('main')
# The initial import.
rev = 23
for path in ('/trunk/interleaved',
'/trunk/interleaved/1',
'/trunk/interleaved/2',
'/trunk/interleaved/3',
'/trunk/interleaved/4',
'/trunk/interleaved/5',
'/trunk/interleaved/a',
'/trunk/interleaved/b',
'/trunk/interleaved/c',
'/trunk/interleaved/d',
'/trunk/interleaved/e',):
if not (logs[rev].changed_paths.get(path) == 'A'):
raise svntest.Failure
if logs[rev].msg.find('Initial revision') != 0:
raise svntest.Failure
# This PEP explains why we pass the 'logs' parameter to these two
# nested functions, instead of just inheriting it from the enclosing
# scope: http://www.python.org/peps/pep-0227.html
def check_letters(rev, logs):
'Return 1 if REV is the rev where only letters were committed, else None.'
for path in ('/trunk/interleaved/a',
'/trunk/interleaved/b',
'/trunk/interleaved/c',
'/trunk/interleaved/d',
'/trunk/interleaved/e',):
if not (logs[rev].changed_paths.get(path) == 'M'):
return None
if logs[rev].msg.find('Committing letters only.') != 0:
return None
return 1
def check_numbers(rev, logs):
'Return 1 if REV is the rev where only numbers were committed, else None.'
for path in ('/trunk/interleaved/1',
'/trunk/interleaved/2',
'/trunk/interleaved/3',
'/trunk/interleaved/4',
'/trunk/interleaved/5',):
if not (logs[rev].changed_paths.get(path) == 'M'):
return None
if logs[rev].msg.find('Committing numbers only.') != 0:
return None
return 1
# One of the commits was letters only, the other was numbers only.
# But they happened "simultaneously", so we don't assume anything
# about which commit appeared first, we just try both ways.
rev = 25
if not ((check_letters(rev, logs) and check_numbers(rev + 1, logs))
or (check_numbers(rev, logs) and check_letters(rev + 1, logs))):
raise svntest.Failure
def simple_tags():
"simple tags"
# See test-data/main-cvsrepos/proj/README.
repos, wc, logs = ensure_conversion('main')
### The actual revision number here (and in other tests) will have
### to change when tags and branches are recognized.
if not logs.has_key(14):
raise svntest.Failure
for path in ('/tags/T_ALL_INITIAL_FILES/proj',
'/tags/T_ALL_INITIAL_FILES/proj/default',
'/tags/T_ALL_INITIAL_FILES/proj/sub1',
'/tags/T_ALL_INITIAL_FILES/proj/sub1/default',
'/tags/T_ALL_INITIAL_FILES/proj/sub1/subsubA',
'/tags/T_ALL_INITIAL_FILES/proj/sub1/subsubA/default',
'/tags/T_ALL_INITIAL_FILES/proj/sub1/subsubB',
'/tags/T_ALL_INITIAL_FILES/proj/sub1/subsubB/default',
'/tags/T_ALL_INITIAL_FILES/proj/sub2',
'/tags/T_ALL_INITIAL_FILES/proj/sub2/default',
'/tags/T_ALL_INITIAL_FILES/proj/sub2/subsubA',
'/tags/T_ALL_INITIAL_FILES/proj/sub2/subsubA/default',
'/tags/T_ALL_INITIAL_FILES/proj/sub3',
'/tags/T_ALL_INITIAL_FILES/proj/sub3/default'):
if not (logs[14].changed_paths.get(path) == 'A'):
raise svntest.Failure
for path in ('/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/default',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/default',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubA',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubA/default',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub2',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub2/default',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub2/subsubA',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub2/subsubA/default',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub3',
'/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub3/default'):
if not (logs[14].changed_paths.get(path) == 'A'):
raise svntest.Failure
# Make sure that other tag does *not* have the missing file:
but_one = '/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB/default'
if logs[14].has_key(but_one):
raise svntest.Failure
### TODO: we also need to check copy history.
### What will the log message be, hmm?
if logs[14].msg.find('') != 0:
raise svntest.Failure
def simple_branch_commits():
"simple branch commits"
# See test-data/main-cvsrepos/proj/README.
repos, wc, logs = ensure_conversion('main')
rev = 21
if not logs.has_key(rev):
raise svntest.Failure
for path in ('/branches/B_MIXED/proj/default',
'/branches/B_MIXED/proj/sub1/default',
'/branches/B_MIXED/proj/sub2/subsubA/default'):
if not (logs[rev].changed_paths.get(path) == 'M'):
raise svntest.Failure
if logs[rev].msg.find('Modify three files, on branch B_MIXED.') != 0:
raise svntest.Failure
def mixed_commit():
"a commit affecting both trunk and a branch"
# See test-data/main-cvsrepos/proj/README.
repos, wc, logs = ensure_conversion('main')
rev = 22
for path in ('/trunk/proj/sub2/default',
'/branches/B_MIXED/proj/sub2/branch_B_MIXED_only'):
if not (logs[rev].changed_paths.get(path) == 'M'):
raise svntest.Failure
if logs[rev].msg.find('A single commit affecting one file on branch B_MIXED '
'and one on trunk.') != 0:
raise svntest.Failure
def split_branch():
"branch created from both trunk and another branch"
# See test-data/split-branch-cvsrepos/README.
#
# The conversion will fail if the bug is present, and
# ensure_conversion would raise svntest.Failure.
repos, wc, logs = ensure_conversion('split-branch')
def resync_misgroups():
"resyncing should not misorder commit groups"
# See test-data/resync-misgroups-cvsrepos/README.
#
# The conversion will fail if the bug is present, and
# ensure_conversion would raise svntest.Failure.
repos, wc, logs = ensure_conversion('resync-misgroups')
#----------------------------------------------------------------------
########################################################################
# Run the tests
# list all tests here, starting with None:
test_list = [ None,
show_usage,
bogus_tag,
overlapping_branch,
tolerate_corruption,
phoenix_branch,
attr_exec,
space_fname,
ctrl_char_in_log,
XFail(overdead),
two_quick,
prune_with_care,
double_delete,
simple_commits,
interleaved_commits,
XFail(simple_tags),
simple_branch_commits,
mixed_commit,
split_branch,
resync_misgroups,
]
if __name__ == '__main__':
svntest.main.run_tests(test_list)
# NOTREACHED
### End of file.