Change the interaction of --eol-from-mime-type and --no-default-eol options.
[cvs2svn.git] / run-tests.py
blobf709747bec95a55464ac1bf18407dfd4f5945bee
1 #!/usr/bin/env python
3 # run_tests.py: test suite for cvs2svn
5 # Usage: run_tests.py [-v | --verbose] [list | <num>]
7 # Options:
8 # -v, --verbose
9 # enable verbose output
11 # Arguments (at most one argument is allowed):
12 # list
13 # If the word "list" is passed as an argument, the list of
14 # available tests is printed (but no tests are run).
16 # <num>
17 # If a number is passed as an argument, then only the test
18 # with that number is run.
20 # If no argument is specified, then all tests are run.
22 # Subversion is a tool for revision control.
23 # See http://subversion.tigris.org for more information.
25 # ====================================================================
26 # Copyright (c) 2000-2004 CollabNet. All rights reserved.
28 # This software is licensed as described in the file COPYING, which
29 # you should have received as part of this distribution. The terms
30 # are also available at http://subversion.tigris.org/license-1.html.
31 # If newer versions of this license are posted there, you may use a
32 # newer version instead, at your option.
34 ######################################################################
36 # General modules
37 import sys
38 import shutil
39 import stat
40 import string
41 import re
42 import os
43 import time
44 import os.path
45 import locale
47 # This script needs to run in the correct directory. Make sure we're there.
48 if not (os.path.exists('cvs2svn') and os.path.exists('test-data')):
49 sys.stderr.write("error: I need to be run in the directory containing "
50 "'cvs2svn' and 'test-data'.\n")
51 sys.exit(1)
53 # Load the Subversion test framework.
54 import svntest
56 # Abbreviations
57 Skip = svntest.testcase.Skip
58 XFail = svntest.testcase.XFail
59 Item = svntest.wc.StateItem
61 cvs2svn = os.path.abspath('cvs2svn')
63 # We use the installed svn and svnlook binaries, instead of using
64 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
65 # behavior -- or even existence -- of local builds shouldn't affect
66 # the cvs2svn test suite.
67 svn = 'svn'
68 svnlook = 'svnlook'
70 test_data_dir = 'test-data'
71 tmp_dir = 'tmp'
74 #----------------------------------------------------------------------
75 # Helpers.
76 #----------------------------------------------------------------------
79 class RunProgramException:
80 pass
82 class MissingErrorException:
83 pass
85 def run_program(program, error_re, *varargs):
86 """Run PROGRAM with VARARGS, return stdout as a list of lines.
87 If there is any stderr and ERROR_RE is None, raise
88 RunProgramException, and print the stderr lines if
89 svntest.main.verbose_mode is true.
91 If ERROR_RE is not None, it is a string regular expression that must
92 match some line of stderr. If it fails to match, raise
93 MissingErrorExpection."""
94 out, err = svntest.main.run_command(program, 1, 0, *varargs)
95 if err:
96 if error_re:
97 for line in err:
98 if re.match(error_re, line):
99 return out
100 raise MissingErrorException
101 else:
102 if svntest.main.verbose_mode:
103 print '\n%s said:\n' % program
104 for line in err:
105 print ' ' + line,
106 print
107 raise RunProgramException
108 return out
111 def run_cvs2svn(error_re, *varargs):
112 """Run cvs2svn with VARARGS, return stdout as a list of lines.
113 If there is any stderr and ERROR_RE is None, raise
114 RunProgramException, and print the stderr lines if
115 svntest.main.verbose_mode is true.
117 If ERROR_RE is not None, it is a string regular expression that must
118 match some line of stderr. If it fails to match, raise
119 MissingErrorException."""
120 # Use the same python that is running this script
121 return run_program(sys.executable, error_re, cvs2svn, *varargs)
122 # On Windows, for an unknown reason, the cmd.exe process invoked by
123 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
124 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
125 # this. Therefore, the redirection of the output to the .s-revs file fails.
126 # We no longer use the problematic invocation on any system, but this
127 # comment remains to warn about this problem.
130 def run_svn(*varargs):
131 """Run svn with VARARGS; return stdout as a list of lines.
132 If there is any stderr, raise RunProgramException, and print the
133 stderr lines if svntest.main.verbose_mode is true."""
134 return run_program(svn, None, *varargs)
137 def repos_to_url(path_to_svn_repos):
138 """This does what you think it does."""
139 rpath = os.path.abspath(path_to_svn_repos)
140 if rpath[0] != '/':
141 rpath = '/' + rpath
142 return 'file://%s' % string.replace(rpath, os.sep, '/')
144 if hasattr(time, 'strptime'):
145 def svn_strptime(timestr):
146 return time.strptime(timestr, '%Y-%m-%d %H:%M:%S')
147 else:
148 # This is for Python earlier than 2.3 on Windows
149 _re_rev_date = re.compile(r'(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)')
150 def svn_strptime(timestr):
151 matches = _re_rev_date.match(timestr).groups()
152 return tuple(map(int, matches)) + (0, 1, -1)
154 class Log:
155 def __init__(self, revision, author, date, symbols):
156 self.revision = revision
157 self.author = author
159 # Internally, we represent the date as seconds since epoch (UTC).
160 # Since standard subversion log output shows dates in localtime
162 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
164 # and time.mktime() converts from localtime, it all works out very
165 # happily.
166 self.date = time.mktime(svn_strptime(date[0:19]))
168 # The following symbols are used for string interpolation when
169 # checking paths:
170 self.symbols = symbols
172 # The changed paths will be accumulated later, as log data is read.
173 # Keys here are paths such as '/trunk/foo/bar', values are letter
174 # codes such as 'M', 'A', and 'D'.
175 self.changed_paths = { }
177 # The msg will be accumulated later, as log data is read.
178 self.msg = ''
180 def absorb_changed_paths(self, out):
181 'Read changed paths from OUT into self, until no more.'
182 while 1:
183 line = out.readline()
184 if len(line) == 1: return
185 line = line[:-1]
186 op_portion = line[3:4]
187 path_portion = line[5:]
188 # If we're running on Windows we get backslashes instead of
189 # forward slashes.
190 path_portion = path_portion.replace('\\', '/')
191 # # We could parse out history information, but currently we
192 # # just leave it in the path portion because that's how some
193 # # tests expect it.
195 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
196 # if m:
197 # path_portion = m.group(1)
198 self.changed_paths[path_portion] = op_portion
200 def __cmp__(self, other):
201 return cmp(self.revision, other.revision) or \
202 cmp(self.author, other.author) or cmp(self.date, other.date) or \
203 cmp(self.changed_paths, other.changed_paths) or \
204 cmp(self.msg, other.msg)
206 def get_path_op(self, path):
207 """Return the operator for the change involving PATH.
209 PATH is allowed to include string interpolation directives (e.g.,
210 '%(trunk)s'), which are interpolated against self.symbols. Return
211 None if there is no record for PATH."""
212 return self.changed_paths.get(path % self.symbols)
214 def check_msg(self, msg):
215 """Verify that this Log's message starts with the specified MSG."""
216 if self.msg.find(msg) != 0:
217 raise svntest.Failure(
218 "Revision %d log message was:\n%s\n\n"
219 "It should have begun with:\n%s\n\n"
220 % (self.revision, self.msg, msg,)
223 def check_change(self, path, op):
224 """Verify that this Log includes a change for PATH with operator OP.
226 PATH is allowed to include string interpolation directives (e.g.,
227 '%(trunk)s'), which are interpolated against self.symbols."""
229 path = path % self.symbols
230 found_op = self.changed_paths.get(path, None)
231 if found_op is None:
232 raise svntest.Failure(
233 "Revision %d does not include change for path %s "
234 "(it should have been %s).\n"
235 % (self.revision, path, op,)
237 if found_op != op:
238 raise svntest.Failure(
239 "Revision %d path %s had op %s (it should have been %s)\n"
240 % (self.revision, path, found_op, op,)
243 def check_changes(self, changed_paths):
244 """Verify that this Log has precisely the CHANGED_PATHS specified.
246 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
247 strings are allowed to include string interpolation directives
248 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
250 cp = {}
251 for (path, op) in changed_paths:
252 cp[path % self.symbols] = op
254 if self.changed_paths != cp:
255 raise svntest.Failure(
256 "Revision %d changed paths list was:\n%s\n\n"
257 "It should have been:\n%s\n\n"
258 % (self.revision, self.changed_paths, cp,)
261 def check(self, msg, changed_paths):
262 """Verify that this Log has the MSG and CHANGED_PATHS specified.
264 Convenience function to check two things at once. MSG is passed
265 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
267 self.check_msg(msg)
268 self.check_changes(changed_paths)
271 def parse_log(svn_repos, symbols):
272 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
274 Initialize the Logs' symbols with SYMBOLS."""
276 class LineFeeder:
277 'Make a list of lines behave like an open file handle.'
278 def __init__(self, lines):
279 self.lines = lines
280 def readline(self):
281 if len(self.lines) > 0:
282 return self.lines.pop(0)
283 else:
284 return None
286 def absorb_message_body(out, num_lines, log):
287 'Read NUM_LINES of log message body from OUT into Log item LOG.'
288 i = 0
289 while i < num_lines:
290 line = out.readline()
291 log.msg += line
292 i += 1
294 log_start_re = re.compile('^r(?P<rev>[0-9]+) \| '
295 '(?P<author>[^\|]+) \| '
296 '(?P<date>[^\|]+) '
297 '\| (?P<lines>[0-9]+) (line|lines)$')
299 log_separator = '-' * 72
301 logs = { }
303 out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos)))
305 while 1:
306 this_log = None
307 line = out.readline()
308 if not line: break
309 line = line[:-1]
311 if line.find(log_separator) == 0:
312 line = out.readline()
313 if not line: break
314 line = line[:-1]
315 m = log_start_re.match(line)
316 if m:
317 this_log = Log(
318 int(m.group('rev')), m.group('author'), m.group('date'), symbols)
319 line = out.readline()
320 if not line.find('Changed paths:') == 0:
321 print 'unexpected log output (missing changed paths)'
322 print "Line: '%s'" % line
323 sys.exit(1)
324 this_log.absorb_changed_paths(out)
325 absorb_message_body(out, int(m.group('lines')), this_log)
326 logs[this_log.revision] = this_log
327 elif len(line) == 0:
328 break # We've reached the end of the log output.
329 else:
330 print 'unexpected log output (missing revision line)'
331 print "Line: '%s'" % line
332 sys.exit(1)
333 else:
334 print 'unexpected log output (missing log separator)'
335 print "Line: '%s'" % line
336 sys.exit(1)
338 return logs
341 def erase(path):
342 """Unconditionally remove PATH and its subtree, if any. PATH may be
343 non-existent, a file or symlink, or a directory."""
344 if os.path.isdir(path):
345 svntest.main.safe_rmtree(path)
346 elif os.path.exists(path):
347 os.remove(path)
350 def sym_log_msg(symbolic_name, is_tag=None):
351 """Return the expected log message for a cvs2svn-synthesized revision
352 creating branch or tag SYMBOLIC_NAME."""
353 # This is a copy-paste of part of cvs2svn's make_revision_props
354 if is_tag:
355 type = 'tag'
356 else:
357 type = 'branch'
359 # In Python 2.2.3, we could use textwrap.fill(). Oh well :-).
360 if len(symbolic_name) >= 13:
361 space_or_newline = '\n'
362 else:
363 space_or_newline = ' '
365 log = "This commit was manufactured by cvs2svn to create %s%s'%s'." \
366 % (type, space_or_newline, symbolic_name)
368 return log
371 def make_conversion_id(name, args, passbypass):
372 """Create an identifying tag for a conversion.
374 The return value can also be used as part of a filesystem path.
376 NAME is the name of the CVS repository.
378 ARGS are the extra arguments to be passed to cvs2svn.
380 PASSBYPASS is a boolean indicating whether the conversion is to be
381 run one pass at a time.
383 The 1-to-1 mapping between cvs2svn command parameters and
384 conversion_ids allows us to avoid running the same conversion more
385 than once, when multiple tests use exactly the same conversion."""
387 conv_id = name
389 _win32_fname_mapping = { '/': '_sl_', '\\': '_bs_', ':': '_co_',
390 '*': '_st_', '?': '_qm_', '"': '_qq_',
391 '<': '_lt_', '>': '_gt_', '|': '_pi_', }
392 for arg in args:
393 # Replace some characters that Win32 isn't happy about having in a
394 # filename (which was causing the eol_mime test to fail).
395 sanitized_arg = arg
396 for a, b in _win32_fname_mapping.items():
397 sanitized_arg = sanitized_arg.replace(a, b)
398 conv_id = conv_id + sanitized_arg
400 if passbypass:
401 conv_id = conv_id + '-passbypass'
403 return conv_id
406 class Conversion:
407 """A record of a cvs2svn conversion.
409 Fields:
411 conv_id -- the conversion id for this Conversion.
413 name -- a one-word name indicating the involved repositories.
415 repos -- the path to the svn repository.
417 logs -- a dictionary of Log instances, as returned by parse_log().
419 symbols -- a dictionary of symbols used for string interpolation
420 in path names.
422 _wc -- the basename of the svn working copy (within tmp_dir).
424 _wc_path -- the path to the svn working copy, if it has already
425 been created; otherwise, None. (The working copy is created
426 lazily when get_wc() is called.)
428 _wc_tree -- the tree built from the svn working copy, if it has
429 already been created; otherwise, None. The tree is created
430 lazily when get_wc_tree() is called.)
432 _svnrepos -- the basename of the svn repository (within tmp_dir)."""
434 def __init__(self, conv_id, name, error_re, passbypass, symbols, args):
435 self.conv_id = conv_id
436 self.name = name
437 self.symbols = symbols
438 if not os.path.isdir(tmp_dir):
439 os.mkdir(tmp_dir)
441 cvsrepos = os.path.abspath(
442 os.path.join(test_data_dir, '%s-cvsrepos' % self.name))
444 saved_wd = os.getcwd()
445 try:
446 os.chdir(tmp_dir)
448 self._svnrepos = '%s-svnrepos' % self.conv_id
449 self.repos = os.path.join(tmp_dir, self._svnrepos)
450 self._wc = '%s-wc' % self.conv_id
451 self._wc_path = None
452 self._wc_tree = None
454 # Clean up from any previous invocations of this script.
455 erase(self._svnrepos)
456 erase(self._wc)
458 try:
459 args.extend( [ '--bdb-txn-nosync', '-s', self._svnrepos, cvsrepos ] )
460 if passbypass:
461 for p in range(1, 9):
462 run_cvs2svn(error_re, '-p', str(p), *args)
463 else:
464 run_cvs2svn(error_re, *args)
465 except RunProgramException:
466 raise svntest.Failure
467 except MissingErrorException:
468 raise svntest.Failure("Test failed because no error matched '%s'"
469 % error_re)
471 if not os.path.isdir(self._svnrepos):
472 raise svntest.Failure("Repository not created: '%s'"
473 % os.path.join(os.getcwd(), self._svnrepos))
475 self.logs = parse_log(self._svnrepos, self.symbols)
476 finally:
477 os.chdir(saved_wd)
479 def find_tag_log(self, tagname):
480 """Search LOGS for a log message containing 'TAGNAME' and return the
481 log in which it was found."""
482 for i in xrange(len(self.logs), 0, -1):
483 if self.logs[i].msg.find("'"+tagname+"'") != -1:
484 return self.logs[i]
485 raise ValueError("Tag %s not found in logs" % tagname)
487 def get_wc(self):
488 """Return the path to the svn working copy. If it has not been
489 created yet, create it now."""
490 if self._wc_path is None:
491 saved_wd = os.getcwd()
492 try:
493 os.chdir(tmp_dir)
494 run_svn('co', repos_to_url(self._svnrepos), self._wc)
495 self._wc_path = os.path.join(tmp_dir, self._wc)
496 finally:
497 os.chdir(saved_wd)
498 return self._wc_path
500 def get_wc_tree(self):
501 if self._wc_tree is None:
502 self._wc_tree = svntest.tree.build_tree_from_wc(self.get_wc(), 1)
503 return self._wc_tree
505 def check_props(self, keys, checks):
506 """Helper function for checking lots of properties. For a list of
507 files in the conversion, check that the values of the properties
508 listed in KEYS agree with those listed in CHECKS. CHECKS is a
509 list of tuples: [ (filename, [value, value, ...]), ...], where the
510 values are listed in the same order as the key names are listed in
511 KEYS."""
513 for (file, values) in checks:
514 assert len(values) == len(keys)
515 props = props_for_path(self.get_wc_tree(), file)
516 for i in range(len(keys)):
517 if props.get(keys[i]) != values[i]:
518 raise svntest.Failure(
519 "File %s has property %s set to \"%s\" "
520 "(it should have been \"%s\").\n"
521 % (file, keys[i], props.get(keys[i]), values[i],)
525 # Cache of conversions that have already been done. Keys are conv_id;
526 # values are Conversion instances.
527 already_converted = { }
529 def ensure_conversion(name, error_re=None, passbypass=None,
530 trunk=None, branches=None, tags=None, args=None):
531 """Convert CVS repository NAME to Subversion, but only if it has not
532 been converted before by this invocation of this script. If it has
533 been converted before, return the Conversion object from the
534 previous invocation.
536 If no error, return a Conversion instance.
538 If ERROR_RE is a string, it is a regular expression expected to
539 match some line of stderr printed by the conversion. If there is an
540 error and ERROR_RE is not set, then raise svntest.Failure.
542 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
543 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
545 NAME is just one word. For example, 'main' would mean to convert
546 './test-data/main-cvsrepos', and after the conversion, the resulting
547 Subversion repository would be in './tmp/main-svnrepos', and a
548 checked out head working copy in './tmp/main-wc'.
550 Any other options to pass to cvs2svn should be in ARGS, each element
551 being one option, e.g., '--trunk-only'. If the option takes an
552 argument, include it directly, e.g., '--mime-types=PATH'. The order
553 of elements in ARGS does not matter.
556 # Copy args into a list, then sort them, so we can construct a
557 # reliable conv_id.
558 if args is None:
559 args = []
560 else:
561 args = list(args)
562 args.sort()
564 if trunk is None:
565 trunk = 'trunk'
566 else:
567 args.append('--trunk=%s' % (trunk,))
569 if branches is None:
570 branches = 'branches'
571 else:
572 args.append('--branches=%s' % (branches,))
574 if tags is None:
575 tags = 'tags'
576 else:
577 args.append('--tags=%s' % (tags,))
579 conv_id = make_conversion_id(name, args, passbypass)
581 if not already_converted.has_key(conv_id):
582 try:
583 # Run the conversion and store the result for the rest of this
584 # session:
585 already_converted[conv_id] = Conversion(
586 conv_id, name, error_re, passbypass,
587 {'trunk' : trunk, 'branches' : branches, 'tags' : tags},
588 args)
589 except svntest.Failure:
590 # Remember the failure so that a future attempt to run this conversion
591 # does not bother to retry, but fails immediately.
592 already_converted[conv_id] = None
593 raise
595 conv = already_converted[conv_id]
596 if conv is None:
597 raise svntest.Failure
598 return conv
601 #----------------------------------------------------------------------
602 # Tests.
603 #----------------------------------------------------------------------
606 def show_usage():
607 "cvs2svn with no arguments shows usage"
608 out = run_cvs2svn(None)
609 if (len(out) > 2 and out[0].find('ERROR:') == 0
610 and out[1].find('DBM module')):
611 print 'cvs2svn cannot execute due to lack of proper DBM module.'
612 print 'Exiting without running any further tests.'
613 sys.exit(1)
614 if out[0].find('USAGE') < 0:
615 raise svntest.Failure('Basic cvs2svn invocation failed.')
618 def attr_exec():
619 "detection of the executable flag"
620 if sys.platform == 'win32':
621 raise svntest.Skip
622 conv = ensure_conversion('main')
623 st = os.stat(
624 os.path.join(conv.get_wc(), 'trunk', 'single-files', 'attr-exec'))
625 if not st[0] & stat.S_IXUSR:
626 raise svntest.Failure
629 def space_fname():
630 "conversion of filename with a space"
631 conv = ensure_conversion('main')
632 if not os.path.exists(
633 os.path.join(conv.get_wc(), 'trunk', 'single-files', 'space fname')):
634 raise svntest.Failure
637 def two_quick():
638 "two commits in quick succession"
639 conv = ensure_conversion('main')
640 logs = parse_log(
641 os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {})
642 if len(logs) != 2:
643 raise svntest.Failure
646 def prune_with_care(**kw):
647 "prune, but never too much"
648 # Robert Pluim encountered this lovely one while converting the
649 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
650 # repository (see issue #1302). Step 4 is the doozy:
652 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
653 # revision 2: adds trunk/blah/NEWS
654 # revision 3: deletes trunk/blah/cookie
655 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
656 # revision 5: does nothing
658 # After fixing cvs2svn, the sequence (correctly) looks like this:
660 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
661 # revision 2: adds trunk/blah/NEWS
662 # revision 3: deletes trunk/blah/cookie
663 # revision 4: does nothing [because trunk/blah/cookie already deleted]
664 # revision 5: deletes blah
666 # The difference is in 4 and 5. In revision 4, it's not correct to
667 # prune blah/, because NEWS is still in there, so revision 4 does
668 # nothing now. But when we delete NEWS in 5, that should bubble up
669 # and prune blah/ instead.
671 # ### Note that empty revisions like 4 are probably going to become
672 # ### at least optional, if not banished entirely from cvs2svn's
673 # ### output. Hmmm, or they may stick around, with an extra
674 # ### revision property explaining what happened. Need to think
675 # ### about that. In some sense, it's a bug in Subversion itself,
676 # ### that such revisions don't show up in 'svn log' output.
678 # In the test below, 'trunk/full-prune/first' represents
679 # cookie, and 'trunk/full-prune/second' represents NEWS.
681 conv = ensure_conversion('main', **kw)
683 # Confirm that revision 4 removes '/trunk/full-prune/first',
684 # and that revision 6 removes '/trunk/full-prune'.
686 # Also confirm similar things about '/full-prune-reappear/...',
687 # which is similar, except that later on it reappears, restored
688 # from pruneland, because a file gets added to it.
690 # And finally, a similar thing for '/partial-prune/...', except that
691 # in its case, a permanent file on the top level prevents the
692 # pruning from going farther than the subdirectory containing first
693 # and second.
695 rev = 11
696 for path in ('/%(trunk)s/full-prune/first',
697 '/%(trunk)s/full-prune-reappear/sub/first',
698 '/%(trunk)s/partial-prune/sub/first'):
699 conv.logs[rev].check_change(path, 'D')
701 rev = 13
702 for path in ('/%(trunk)s/full-prune',
703 '/%(trunk)s/full-prune-reappear',
704 '/%(trunk)s/partial-prune/sub'):
705 conv.logs[rev].check_change(path, 'D')
707 rev = 47
708 for path in ('/%(trunk)s/full-prune-reappear',
709 '/%(trunk)s/full-prune-reappear/appears-later'):
710 conv.logs[rev].check_change(path, 'A')
713 def prune_with_care_variants():
714 "prune, with alternate repo layout"
715 prune_with_care(trunk='a', branches='b', tags='c')
716 prune_with_care(trunk='a/1', branches='b/1', tags='c/1')
717 prune_with_care(trunk='a/1', branches='a/2', tags='a/3')
720 def interleaved_commits():
721 "two interleaved trunk commits, different log msgs"
722 # See test-data/main-cvsrepos/proj/README.
723 conv = ensure_conversion('main')
725 # The initial import.
726 rev = 37
727 conv.logs[rev].check('Initial revision', (
728 ('/%(trunk)s/interleaved', 'A'),
729 ('/%(trunk)s/interleaved/1', 'A'),
730 ('/%(trunk)s/interleaved/2', 'A'),
731 ('/%(trunk)s/interleaved/3', 'A'),
732 ('/%(trunk)s/interleaved/4', 'A'),
733 ('/%(trunk)s/interleaved/5', 'A'),
734 ('/%(trunk)s/interleaved/a', 'A'),
735 ('/%(trunk)s/interleaved/b', 'A'),
736 ('/%(trunk)s/interleaved/c', 'A'),
737 ('/%(trunk)s/interleaved/d', 'A'),
738 ('/%(trunk)s/interleaved/e', 'A'),
741 # This PEP explains why we pass the 'log' parameter to these two
742 # nested functions, instead of just inheriting it from the enclosing
743 # scope: http://www.python.org/peps/pep-0227.html
745 def check_letters(log):
746 """Check if REV is the rev where only letters were committed."""
747 log.check('Committing letters only.', (
748 ('/%(trunk)s/interleaved/a', 'M'),
749 ('/%(trunk)s/interleaved/b', 'M'),
750 ('/%(trunk)s/interleaved/c', 'M'),
751 ('/%(trunk)s/interleaved/d', 'M'),
752 ('/%(trunk)s/interleaved/e', 'M'),
755 def check_numbers(log):
756 """Check if REV is the rev where only numbers were committed."""
757 log.check('Committing numbers only.', (
758 ('/%(trunk)s/interleaved/1', 'M'),
759 ('/%(trunk)s/interleaved/2', 'M'),
760 ('/%(trunk)s/interleaved/3', 'M'),
761 ('/%(trunk)s/interleaved/4', 'M'),
762 ('/%(trunk)s/interleaved/5', 'M'),
765 # One of the commits was letters only, the other was numbers only.
766 # But they happened "simultaneously", so we don't assume anything
767 # about which commit appeared first, so we just try both ways.
768 rev = rev + 3
769 try:
770 check_letters(conv.logs[rev])
771 check_numbers(conv.logs[rev + 1])
772 except svntest.Failure:
773 check_numbers(conv.logs[rev])
774 check_letters(conv.logs[rev + 1])
777 def simple_commits():
778 "simple trunk commits"
779 # See test-data/main-cvsrepos/proj/README.
780 conv = ensure_conversion('main')
782 # The initial import.
783 rev = 23
784 conv.logs[rev].check('Initial revision', (
785 ('/%(trunk)s/proj', 'A'),
786 ('/%(trunk)s/proj/default', 'A'),
787 ('/%(trunk)s/proj/sub1', 'A'),
788 ('/%(trunk)s/proj/sub1/default', 'A'),
789 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
790 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
791 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
792 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
793 ('/%(trunk)s/proj/sub2', 'A'),
794 ('/%(trunk)s/proj/sub2/default', 'A'),
795 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
796 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
797 ('/%(trunk)s/proj/sub3', 'A'),
798 ('/%(trunk)s/proj/sub3/default', 'A'),
801 # The first commit.
802 rev = 30
803 conv.logs[rev].check('First commit to proj, affecting two files.', (
804 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
805 ('/%(trunk)s/proj/sub3/default', 'M'),
808 # The second commit.
809 rev = 31
810 conv.logs[rev].check('Second commit to proj, affecting all 7 files.', (
811 ('/%(trunk)s/proj/default', 'M'),
812 ('/%(trunk)s/proj/sub1/default', 'M'),
813 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
814 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
815 ('/%(trunk)s/proj/sub2/default', 'M'),
816 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
817 ('/%(trunk)s/proj/sub3/default', 'M')
821 def simple_tags(**kw):
822 "simple tags and branches with no commits"
823 # See test-data/main-cvsrepos/proj/README.
824 conv = ensure_conversion('main', **kw)
826 # Verify the copy source for the tags we are about to check
827 # No need to verify the copyfrom revision, as simple_commits did that
828 conv.logs[24].check(sym_log_msg('vendorbranch'), (
829 ('/%(branches)s/vendorbranch/proj (from /%(trunk)s/proj:23)', 'A'),
832 fromstr = ' (from /%(branches)s/vendorbranch:25)'
834 # Tag on rev 1.1.1.1 of all files in proj
835 log = conv.find_tag_log('T_ALL_INITIAL_FILES')
836 log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
837 ('/%(tags)s/T_ALL_INITIAL_FILES'+fromstr, 'A'),
838 ('/%(tags)s/T_ALL_INITIAL_FILES/single-files', 'D'),
839 ('/%(tags)s/T_ALL_INITIAL_FILES/partial-prune', 'D'),
842 # The same, as a branch
843 conv.logs[26].check(sym_log_msg('B_FROM_INITIALS'), (
844 ('/%(branches)s/B_FROM_INITIALS'+fromstr, 'A'),
845 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
846 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
849 # Tag on rev 1.1.1.1 of all files in proj, except one
850 log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
851 log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
852 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr, 'A'),
853 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/single-files', 'D'),
854 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune', 'D'),
855 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
858 # The same, as a branch
859 conv.logs[27].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
860 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE'+fromstr, 'A'),
861 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'),
862 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'),
863 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
867 def simple_tags_variants():
868 "simple tags, with alternate repo layout"
869 simple_tags(trunk='a', branches='b', tags='c')
870 simple_tags(trunk='a/1', branches='b/1', tags='c/1')
871 simple_tags(trunk='a/1', branches='a/2', tags='a/3')
874 def simple_branch_commits():
875 "simple branch commits"
876 # See test-data/main-cvsrepos/proj/README.
877 conv = ensure_conversion('main')
879 rev = 35
880 conv.logs[rev].check('Modify three files, on branch B_MIXED.', (
881 ('/%(branches)s/B_MIXED/proj/default', 'M'),
882 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
883 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
887 def mixed_time_tag():
888 "mixed-time tag"
889 # See test-data/main-cvsrepos/proj/README.
890 conv = ensure_conversion('main')
892 log = conv.find_tag_log('T_MIXED')
893 expected = (
894 ('/%(tags)s/T_MIXED (from /%(trunk)s:31)', 'A'),
895 ('/%(tags)s/T_MIXED/partial-prune', 'D'),
896 ('/%(tags)s/T_MIXED/single-files', 'D'),
897 ('/%(tags)s/T_MIXED/proj/sub2/subsubA '
898 '(from /%(trunk)s/proj/sub2/subsubA:23)', 'R'),
899 ('/%(tags)s/T_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:30)', 'R'),
901 if log.revision == 16:
902 expected.append(('/%(tags)s', 'A'))
903 log.check_changes(expected)
906 def mixed_time_branch_with_added_file():
907 "mixed-time branch, and a file added to the branch"
908 # See test-data/main-cvsrepos/proj/README.
909 conv = ensure_conversion('main')
911 # A branch from the same place as T_MIXED in the previous test,
912 # plus a file added directly to the branch
913 conv.logs[32].check(sym_log_msg('B_MIXED'), (
914 ('/%(branches)s/B_MIXED (from /%(trunk)s:31)', 'A'),
915 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
916 ('/%(branches)s/B_MIXED/single-files', 'D'),
917 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
918 '(from /%(trunk)s/proj/sub2/subsubA:23)', 'R'),
919 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:30)', 'R'),
922 conv.logs[34].check('Add a file on branch B_MIXED.', (
923 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
927 def mixed_commit():
928 "a commit affecting both trunk and a branch"
929 # See test-data/main-cvsrepos/proj/README.
930 conv = ensure_conversion('main')
932 conv.logs[36].check(
933 'A single commit affecting one file on branch B_MIXED '
934 'and one on trunk.', (
935 ('/%(trunk)s/proj/sub2/default', 'M'),
936 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
940 def split_time_branch():
941 "branch some trunk files, and later branch the rest"
942 # See test-data/main-cvsrepos/proj/README.
943 conv = ensure_conversion('main')
945 rev = 42
946 # First change on the branch, creating it
947 conv.logs[rev].check(sym_log_msg('B_SPLIT'), (
948 ('/%(branches)s/B_SPLIT (from /%(trunk)s:36)', 'A'),
949 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
950 ('/%(branches)s/B_SPLIT/single-files', 'D'),
951 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
954 conv.logs[rev + 1].check('First change on branch B_SPLIT.', (
955 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
956 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
957 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
958 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
959 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
962 # A trunk commit for the file which was not branched
963 conv.logs[rev + 2].check('A trunk change to sub1/subsubB/default. '
964 'This was committed about an', (
965 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
968 # Add the file not already branched to the branch, with modification:w
969 conv.logs[rev + 3].check(sym_log_msg('B_SPLIT'), (
970 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
971 '(from /%(trunk)s/proj/sub1/subsubB:44)', 'A'),
974 conv.logs[rev + 4].check('This change affects sub3/default and '
975 'sub1/subsubB/default, on branch', (
976 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
977 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
981 def bogus_tag():
982 "conversion of invalid symbolic names"
983 conv = ensure_conversion('bogus-tag')
986 def overlapping_branch():
987 "ignore a file with a branch with two names"
988 conv = ensure_conversion('overlapping-branch',
989 error_re='.*cannot also have name \'vendorB\'')
990 rev = 4
991 conv.logs[rev].check_change('/%(branches)s/vendorA (from /%(trunk)s:3)',
992 'A')
993 # We don't know what order the first two commits would be in, since
994 # they have different log messages but the same timestamps. As only
995 # one of the files would be on the vendorB branch in the regression
996 # case being tested here, we allow for either order.
997 if (conv.logs[rev].get_path_op(
998 '/%(branches)s/vendorB (from /%(trunk)s:2)') == 'A'
999 or conv.logs[rev].get_path_op(
1000 '/%(branches)s/vendorB (from /%(trunk)s:3)') == 'A'):
1001 raise svntest.Failure
1002 conv.logs[rev + 1].check_changes(())
1003 if len(conv.logs) != rev + 1:
1004 raise svntest.Failure
1007 def phoenix_branch(**kw):
1008 "convert a branch file rooted in a 'dead' revision"
1009 conv = ensure_conversion('phoenix', **kw)
1010 conv.logs[8].check(sym_log_msg('volsung_20010721'), (
1011 ('/%(branches)s/volsung_20010721 (from /%(trunk)s:7)', 'A'),
1012 ('/%(branches)s/volsung_20010721/file.txt', 'D'),
1014 conv.logs[9].check('This file was supplied by Jack Moffitt', (
1015 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1019 def phoenix_branch_variants():
1020 "'dead' revision, with alternate repo layout"
1021 phoenix_branch(trunk='a/1', branches='b/1', tags='c/1')
1024 ###TODO: We check for 4 changed paths here to accomodate creating tags
1025 ###and branches in rev 1, but that will change, so this will
1026 ###eventually change back.
1027 def ctrl_char_in_log():
1028 "handle a control char in a log message"
1029 # This was issue #1106.
1030 rev = 2
1031 conv = ensure_conversion('ctrl-char-in-log')
1032 conv.logs[rev].check_changes((
1033 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1035 if conv.logs[rev].msg.find('\x04') < 0:
1036 raise svntest.Failure(
1037 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1040 def overdead():
1041 "handle tags rooted in a redeleted revision"
1042 conv = ensure_conversion('overdead')
1045 def no_trunk_prune(**kw):
1046 "ensure that trunk doesn't get pruned"
1047 conv = ensure_conversion('overdead', **kw)
1048 for rev in conv.logs.keys():
1049 rev_logs = conv.logs[rev]
1050 if rev_logs.get_path_op('/%(trunk)s') == 'D':
1051 raise svntest.Failure
1054 def no_trunk_prune_variants():
1055 "no trunk pruning, with alternate repo layout"
1056 no_trunk_prune(trunk='a', branches='b', tags='c')
1057 no_trunk_prune(trunk='a/1', branches='b/1', tags='c/1')
1058 no_trunk_prune(trunk='a/1', branches='a/2', tags='a/3')
1061 def double_delete():
1062 "file deleted twice, in the root of the repository"
1063 # This really tests several things: how we handle a file that's
1064 # removed (state 'dead') in two successive revisions; how we
1065 # handle a file in the root of the repository (there were some
1066 # bugs in cvs2svn's svn path construction for top-level files); and
1067 # the --no-prune option.
1068 conv = ensure_conversion(
1069 'double-delete', args=['--trunk-only', '--no-prune'])
1071 path = '/%(trunk)s/twice-removed'
1072 rev = 2
1073 conv.logs[rev].check_change(path, 'A')
1074 conv.logs[rev].check_msg('Initial revision')
1076 conv.logs[rev + 1].check_change(path, 'D')
1077 conv.logs[rev + 1].check_msg('Remove this file for the first time.')
1079 if conv.logs[rev + 1].get_path_op('/%(trunk)s') is not None:
1080 raise svntest.Failure
1083 def split_branch():
1084 "branch created from both trunk and another branch"
1085 # See test-data/split-branch-cvsrepos/README.
1087 # The conversion will fail if the bug is present, and
1088 # ensure_conversion will raise svntest.Failure.
1089 conv = ensure_conversion('split-branch')
1092 def resync_misgroups():
1093 "resyncing should not misorder commit groups"
1094 # See test-data/resync-misgroups-cvsrepos/README.
1096 # The conversion will fail if the bug is present, and
1097 # ensure_conversion will raise svntest.Failure.
1098 conv = ensure_conversion('resync-misgroups')
1101 def tagged_branch_and_trunk(**kw):
1102 "allow tags with mixed trunk and branch sources"
1103 conv = ensure_conversion('tagged-branch-n-trunk', **kw)
1105 tags = kw.get('tags', 'tags')
1107 a_path = os.path.join(conv.get_wc(), tags, 'some-tag', 'a.txt')
1108 b_path = os.path.join(conv.get_wc(), tags, 'some-tag', 'b.txt')
1109 if not (os.path.exists(a_path) and os.path.exists(b_path)):
1110 raise svntest.Failure
1111 if (open(a_path, 'r').read().find('1.24') == -1) \
1112 or (open(b_path, 'r').read().find('1.5') == -1):
1113 raise svntest.Failure
1116 def tagged_branch_and_trunk_variants():
1117 "mixed tags, with alternate repo layout"
1118 tagged_branch_and_trunk(trunk='a/1', branches='a/2', tags='a/3')
1121 def enroot_race():
1122 "never use the rev-in-progress as a copy source"
1123 # See issue #1427 and r8544.
1124 conv = ensure_conversion('enroot-race')
1125 rev = 8
1126 conv.logs[rev].check_changes((
1127 ('/%(branches)s/mybranch (from /%(trunk)s:7)', 'A'),
1128 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1129 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1131 conv.logs[rev + 1].check_changes((
1132 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1133 ('/%(trunk)s/proj/a.txt', 'M'),
1134 ('/%(trunk)s/proj/b.txt', 'M'),
1138 def enroot_race_obo():
1139 "do use the last completed rev as a copy source"
1140 conv = ensure_conversion('enroot-race-obo')
1141 conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1142 if not len(conv.logs) == 3:
1143 raise svntest.Failure
1146 def branch_delete_first(**kw):
1147 "correctly handle deletion as initial branch action"
1148 # See test-data/branch-delete-first-cvsrepos/README.
1150 # The conversion will fail if the bug is present, and
1151 # ensure_conversion would raise svntest.Failure.
1152 conv = ensure_conversion('branch-delete-first', **kw)
1154 branches = kw.get('branches', 'branches')
1156 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1157 if os.path.exists(
1158 os.path.join(conv.get_wc(), branches, 'branch-1', 'file')):
1159 raise svntest.Failure
1160 if os.path.exists(
1161 os.path.join(conv.get_wc(), branches, 'branch-2', 'file')):
1162 raise svntest.Failure
1163 if not os.path.exists(
1164 os.path.join(conv.get_wc(), branches, 'branch-3', 'file')):
1165 raise svntest.Failure
1168 def branch_delete_first_variants():
1169 "initial delete, with alternate repo layout"
1170 branch_delete_first(trunk='a/1', branches='a/2', tags='a/3')
1173 def nonascii_filenames():
1174 "non ascii files converted incorrectly"
1175 # see issue #1255
1177 # on a en_US.iso-8859-1 machine this test fails with
1178 # svn: Can't recode ...
1180 # as described in the issue
1182 # on a en_US.UTF-8 machine this test fails with
1183 # svn: Malformed XML ...
1185 # which means at least it fails. Unfortunately it won't fail
1186 # with the same error...
1188 # mangle current locale settings so we know we're not running
1189 # a UTF-8 locale (which does not exhibit this problem)
1190 current_locale = locale.getlocale()
1191 new_locale = 'en_US.ISO8859-1'
1192 locale_changed = None
1194 # From http://docs.python.org/lib/module-sys.html
1196 # getfilesystemencoding():
1198 # Return the name of the encoding used to convert Unicode filenames
1199 # into system file names, or None if the system default encoding is
1200 # used. The result value depends on the operating system:
1202 # - On Windows 9x, the encoding is ``mbcs''.
1203 # - On Mac OS X, the encoding is ``utf-8''.
1204 # - On Unix, the encoding is the user's preference according to the
1205 # result of nl_langinfo(CODESET), or None if the
1206 # nl_langinfo(CODESET) failed.
1207 # - On Windows NT+, file names are Unicode natively, so no conversion is
1208 # performed.
1210 # So we're going to skip this test on Mac OS X for now.
1211 if sys.platform == "darwin":
1212 raise svntest.Skip
1214 try:
1215 # change locale to non-UTF-8 locale to generate latin1 names
1216 locale.setlocale(locale.LC_ALL, # this might be too broad?
1217 new_locale)
1218 locale_changed = 1
1219 except locale.Error:
1220 raise svntest.Skip
1222 try:
1223 testdata_path = os.path.abspath('test-data')
1224 srcrepos_path = os.path.join(testdata_path,'main-cvsrepos')
1225 dstrepos_path = os.path.join(testdata_path,'non-ascii-cvsrepos')
1226 if not os.path.exists(dstrepos_path):
1227 # create repos from existing main repos
1228 shutil.copytree(srcrepos_path, dstrepos_path)
1229 base_path = os.path.join(dstrepos_path, 'single-files')
1230 shutil.copyfile(os.path.join(base_path, 'twoquick,v'),
1231 os.path.join(base_path, 'two\366uick,v'))
1232 new_path = os.path.join(dstrepos_path, 'single\366files')
1233 os.rename(base_path, new_path)
1235 # if ensure_conversion can generate a
1236 conv = ensure_conversion('non-ascii', args=['--encoding=latin1'])
1237 finally:
1238 if locale_changed:
1239 locale.setlocale(locale.LC_ALL, current_locale)
1240 svntest.main.safe_rmtree(dstrepos_path)
1243 def vendor_branch_sameness():
1244 "avoid spurious changes for initial revs"
1245 conv = ensure_conversion('vendor-branch-sameness')
1247 # There are four files in the repository:
1249 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1250 # the same contents, the file's default branch is 1.1.1,
1251 # and both revisions are in state 'Exp'.
1253 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1254 # 1.1 (the addition of a line of text).
1256 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1258 # d.txt: This file was created by 'cvs add' instead of import, so
1259 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1260 # The timestamp on the add is exactly the same as for the
1261 # imports of the other files.
1263 # (Log messages for the same revisions are the same in all files.)
1265 # What we expect to see is everyone added in r1, then trunk/proj
1266 # copied in r2. In the copy, only a.txt should be left untouched;
1267 # b.txt should be 'M'odified, and (for different reasons) c.txt and
1268 # d.txt should be 'D'eleted.
1270 rev = 2
1271 conv.logs[rev].check('Initial revision', (
1272 ('/%(trunk)s/proj', 'A'),
1273 ('/%(trunk)s/proj/a.txt', 'A'),
1274 ('/%(trunk)s/proj/b.txt', 'A'),
1275 ('/%(trunk)s/proj/c.txt', 'A'),
1276 ('/%(trunk)s/proj/d.txt', 'A'),
1279 conv.logs[rev + 1].check(sym_log_msg('vbranchA'), (
1280 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1281 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1284 conv.logs[rev + 2].check('First vendor branch revision.', (
1285 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1286 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1290 def default_branches():
1291 "handle default branches correctly"
1292 conv = ensure_conversion('default-branches')
1294 # There are seven files in the repository:
1296 # a.txt:
1297 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1298 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1299 # committed (thus losing the default branch "1.1.1"), then
1300 # 1.1.1.4 was imported. All vendor import release tags are
1301 # still present.
1303 # b.txt:
1304 # Like a.txt, but without rev 1.2.
1306 # c.txt:
1307 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1309 # d.txt:
1310 # Same as the previous two, but 1.1.1 branch is unlabeled.
1312 # e.txt:
1313 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1315 # deleted-on-vendor-branch.txt,v:
1316 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1318 # added-then-imported.txt,v:
1319 # Added with 'cvs add' to create 1.1, then imported with
1320 # completely different contents to create 1.1.1.1, therefore
1321 # never had a default branch.
1324 conv.logs[18].check(sym_log_msg('vtag-4',1), (
1325 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:16)', 'A'),
1326 ('/%(tags)s/vtag-4/proj/d.txt '
1327 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:16)', 'A'),
1330 conv.logs[6].check(sym_log_msg('vtag-1',1), (
1331 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:5)', 'A'),
1332 ('/%(tags)s/vtag-1/proj/d.txt '
1333 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1336 conv.logs[9].check(sym_log_msg('vtag-2',1), (
1337 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:7)', 'A'),
1338 ('/%(tags)s/vtag-2/proj/d.txt '
1339 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:7)', 'A'),
1342 conv.logs[12].check(sym_log_msg('vtag-3',1), (
1343 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:10)', 'A'),
1344 ('/%(tags)s/vtag-3/proj/d.txt '
1345 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:10)', 'A'),
1346 ('/%(tags)s/vtag-3/proj/e.txt '
1347 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:10)', 'A'),
1350 conv.logs[17].check("This commit was generated by cvs2svn "
1351 "to compensate for changes in r16,", (
1352 ('/%(trunk)s/proj/b.txt '
1353 '(from /%(branches)s/vbranchA/proj/b.txt:16)', 'R'),
1354 ('/%(trunk)s/proj/c.txt '
1355 '(from /%(branches)s/vbranchA/proj/c.txt:16)', 'R'),
1356 ('/%(trunk)s/proj/d.txt '
1357 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:16)', 'R'),
1358 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1359 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:16)',
1360 'A'),
1361 ('/%(trunk)s/proj/e.txt '
1362 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:16)', 'R'),
1365 conv.logs[16].check("Import (vbranchA, vtag-4).", (
1366 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1367 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1368 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1369 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1370 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1371 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1372 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1375 conv.logs[15].check(sym_log_msg('vbranchA'), (
1376 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1377 '(from /%(trunk)s/proj/added-then-imported.txt:14)', 'A'),
1380 conv.logs[14].check("Add a file to the working copy.", (
1381 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1384 conv.logs[13].check("First regular commit, to a.txt, on vtag-3.", (
1385 ('/%(trunk)s/proj/a.txt', 'M'),
1388 conv.logs[11].check("This commit was generated by cvs2svn "
1389 "to compensate for changes in r10,", (
1390 ('/%(trunk)s/proj/a.txt '
1391 '(from /%(branches)s/vbranchA/proj/a.txt:10)', 'R'),
1392 ('/%(trunk)s/proj/b.txt '
1393 '(from /%(branches)s/vbranchA/proj/b.txt:10)', 'R'),
1394 ('/%(trunk)s/proj/c.txt '
1395 '(from /%(branches)s/vbranchA/proj/c.txt:10)', 'R'),
1396 ('/%(trunk)s/proj/d.txt '
1397 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:10)', 'R'),
1398 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1399 ('/%(trunk)s/proj/e.txt '
1400 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:10)', 'R'),
1403 conv.logs[10].check("Import (vbranchA, vtag-3).", (
1404 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1405 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1406 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1407 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1408 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1409 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1412 conv.logs[8].check("This commit was generated by cvs2svn "
1413 "to compensate for changes in r7,", (
1414 ('/%(trunk)s/proj/a.txt '
1415 '(from /%(branches)s/vbranchA/proj/a.txt:7)', 'R'),
1416 ('/%(trunk)s/proj/b.txt '
1417 '(from /%(branches)s/vbranchA/proj/b.txt:7)', 'R'),
1418 ('/%(trunk)s/proj/c.txt '
1419 '(from /%(branches)s/vbranchA/proj/c.txt:7)', 'R'),
1420 ('/%(trunk)s/proj/d.txt '
1421 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:7)', 'R'),
1422 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1423 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:7)',
1424 'R'),
1425 ('/%(trunk)s/proj/e.txt '
1426 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:7)', 'R'),
1429 conv.logs[7].check("Import (vbranchA, vtag-2).", (
1430 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1431 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1432 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1433 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1434 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1435 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1438 conv.logs[5].check("Import (vbranchA, vtag-1).", ())
1440 conv.logs[4].check(sym_log_msg('vbranchA'), (
1441 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1442 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1443 ('/%(branches)s/vbranchA/proj/e.txt', 'D'),
1446 conv.logs[3].check(sym_log_msg('unlabeled-1.1.1'), (
1447 ('/%(branches)s/unlabeled-1.1.1 (from /%(trunk)s:2)', 'A'),
1448 ('/%(branches)s/unlabeled-1.1.1/proj/a.txt', 'D'),
1449 ('/%(branches)s/unlabeled-1.1.1/proj/b.txt', 'D'),
1450 ('/%(branches)s/unlabeled-1.1.1/proj/c.txt', 'D'),
1451 ('/%(branches)s/unlabeled-1.1.1/proj/deleted-on-vendor-branch.txt', 'D'),
1454 conv.logs[2].check("Initial revision", (
1455 ('/%(trunk)s/proj', 'A'),
1456 ('/%(trunk)s/proj/a.txt', 'A'),
1457 ('/%(trunk)s/proj/b.txt', 'A'),
1458 ('/%(trunk)s/proj/c.txt', 'A'),
1459 ('/%(trunk)s/proj/d.txt', 'A'),
1460 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1461 ('/%(trunk)s/proj/e.txt', 'A'),
1465 def compose_tag_three_sources():
1466 "compose a tag from three sources"
1467 conv = ensure_conversion('compose-tag-three-sources')
1469 conv.logs[2].check("Add on trunk", (
1470 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
1471 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
1472 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
1473 ('/%(trunk)s/tagged-on-b1', 'A'),
1474 ('/%(trunk)s/tagged-on-b2', 'A'),
1477 conv.logs[3].check(sym_log_msg('b1'), (
1478 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
1481 conv.logs[4].check(sym_log_msg('b2'), (
1482 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
1485 conv.logs[5].check("Commit on branch b1", (
1486 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
1487 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
1488 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
1489 ('/%(branches)s/b1/tagged-on-b1', 'M'),
1490 ('/%(branches)s/b1/tagged-on-b2', 'M'),
1493 conv.logs[6].check("Commit on branch b2", (
1494 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
1495 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
1496 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
1497 ('/%(branches)s/b2/tagged-on-b1', 'M'),
1498 ('/%(branches)s/b2/tagged-on-b2', 'M'),
1501 conv.logs[7].check("Commit again on trunk", (
1502 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
1503 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
1504 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
1505 ('/%(trunk)s/tagged-on-b1', 'M'),
1506 ('/%(trunk)s/tagged-on-b2', 'M'),
1509 conv.logs[8].check(sym_log_msg('T',1), (
1510 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
1511 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:7)', 'R'),
1512 ('/%(tags)s/T/tagged-on-trunk-1.1 '
1513 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
1514 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:7)', 'R'),
1518 def pass5_when_to_fill():
1519 "reserve a svn revnum for a fill only when required"
1520 # The conversion will fail if the bug is present, and
1521 # ensure_conversion would raise svntest.Failure.
1522 conv = ensure_conversion('pass5-when-to-fill')
1525 def empty_trunk(**kw):
1526 "don't break when the trunk is empty"
1527 # The conversion will fail if the bug is present, and
1528 # ensure_conversion would raise svntest.Failure.
1529 conv = ensure_conversion('empty-trunk', **kw)
1532 def empty_trunk_variants():
1533 "empty trunk, with alternate repo layout"
1534 empty_trunk(trunk='a', branches='b', tags='c')
1535 empty_trunk(trunk='a/1', branches='a/2', tags='a/3')
1538 def no_spurious_svn_commits():
1539 "ensure that we don't create any spurious commits"
1540 conv = ensure_conversion('phoenix')
1542 # Check spurious commit that could be created in CVSCommit._pre_commit
1543 # (When you add a file on a branch, CVS creates a trunk revision
1544 # in state 'dead'. If the log message of that commit is equal to
1545 # the one that CVS generates, we do not ever create a 'fill'
1546 # SVNCommit for it.)
1548 # and spurious commit that could be created in CVSCommit._commit
1549 # (When you add a file on a branch, CVS creates a trunk revision
1550 # in state 'dead'. If the log message of that commit is equal to
1551 # the one that CVS generates, we do not create a primary SVNCommit
1552 # for it.)
1553 conv.logs[18].check('File added on branch xiphophorus', (
1554 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
1557 # Check to make sure that a commit *is* generated:
1558 # (When you add a file on a branch, CVS creates a trunk revision
1559 # in state 'dead'. If the log message of that commit is NOT equal
1560 # to the one that CVS generates, we create a primary SVNCommit to
1561 # serve as a home for the log message in question.
1562 conv.logs[19].check('file added-on-branch2.txt was initially added on '
1563 + 'branch xiphophorus,\nand this log message was tweaked', ())
1565 # Check spurious commit that could be created in
1566 # CVSRevisionAggregator.attempt_to_commit_symbols
1567 # (We shouldn't consider a CVSRevision whose op is OP_DEAD as a
1568 # candidate for the LastSymbolicNameDatabase.
1569 conv.logs[20].check('This file was also added on branch xiphophorus,', (
1570 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
1574 def peer_path_pruning(**kw):
1575 "make sure that filling prunes paths correctly"
1576 conv = ensure_conversion('peer-path-pruning', **kw)
1577 conv.logs[8].check(sym_log_msg('BRANCH'), (
1578 ('/%(branches)s/BRANCH (from /%(trunk)s:6)', 'A'),
1579 ('/%(branches)s/BRANCH/bar', 'D'),
1580 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:7)', 'R'),
1584 def peer_path_pruning_variants():
1585 "filling prune paths, with alternate repo layout"
1586 peer_path_pruning(trunk='a/1', branches='a/2', tags='a/3')
1589 def invalid_closings_on_trunk():
1590 "verify correct revs are copied to default branches"
1591 # The conversion will fail if the bug is present, and
1592 # ensure_conversion would raise svntest.Failure.
1593 conv = ensure_conversion('invalid-closings-on-trunk')
1596 def individual_passes():
1597 "run each pass individually"
1598 conv = ensure_conversion('main')
1599 conv2 = ensure_conversion('main', passbypass=1)
1601 if conv.logs != conv2.logs:
1602 raise svntest.Failure
1605 def resync_bug():
1606 "reveal a big bug in our resync algorithm"
1607 # This will fail if the bug is present
1608 conv = ensure_conversion('resync-bug')
1611 def branch_from_default_branch():
1612 "reveal a bug in our default branch detection code"
1613 conv = ensure_conversion('branch-from-default-branch')
1615 # This revision will be a default branch synchronization only
1616 # if cvs2svn is correctly determining default branch revisions.
1618 # The bug was that cvs2svn was treating revisions on branches off of
1619 # default branches as default branch revisions, resulting in
1620 # incorrectly regarding the branch off of the default branch as a
1621 # non-trunk default branch. Crystal clear? I thought so. See
1622 # issue #42 for more incoherent blathering.
1623 conv.logs[6].check("This commit was generated by cvs2svn", (
1624 ('/%(trunk)s/proj/file.txt '
1625 '(from /%(branches)s/upstream/proj/file.txt:5)', 'R'),
1628 def file_in_attic_too():
1629 "die if a file exists in and out of the attic"
1630 try:
1631 ensure_conversion('file-in-attic-too')
1632 raise MissingErrorException
1633 except svntest.Failure:
1634 pass
1636 def symbolic_name_filling_guide():
1637 "reveal a big bug in our SymbolicNameFillingGuide"
1638 # This will fail if the bug is present
1639 conv = ensure_conversion('symbolic-name-overfill')
1642 # Helpers for tests involving file contents and properties.
1644 class NodeTreeWalkException:
1645 "Exception class for node tree traversals."
1646 pass
1648 def node_for_path(node, path):
1649 "In the tree rooted under SVNTree NODE, return the node at PATH."
1650 if node.name != '__SVN_ROOT_NODE':
1651 raise NodeTreeWalkException
1652 path = path.strip('/')
1653 components = path.split('/')
1654 for component in components:
1655 node = svntest.tree.get_child(node, component)
1656 return node
1658 # Helper for tests involving properties.
1659 def props_for_path(node, path):
1660 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
1661 return node_for_path(node, path).props
1664 def eol_mime():
1665 "test eol settings and mime types together"
1666 ###TODO: It's a bit klugey to construct this path here. But so far
1667 ### there's only one test with a mime.types file. If we have more,
1668 ### we should abstract this into some helper, which would be located
1669 ### near ensure_conversion(). Note that it is a convention of this
1670 ### test suite for a mime.types file to be located in the top level
1671 ### of the CVS repository to which it applies.
1672 mime_path = os.path.abspath(os.path.join(test_data_dir,
1673 'eol-mime-cvsrepos',
1674 'mime.types'))
1676 # We do four conversions. Each time, we pass --mime-types=FILE with
1677 # the same FILE, but vary --no-default-eol and --eol-from-mime-type.
1678 # Thus there's one conversion with neither flag, one with just the
1679 # former, one with just the latter, and one with both.
1681 # In two of the four conversions, we pass --cvs-revnums to make
1682 # certain that there are no bad interactions.
1684 # The files are as follows:
1686 # trunk/foo.txt: no -kb, mime file says nothing.
1687 # trunk/foo.xml: no -kb, mime file says text.
1688 # trunk/foo.zip: no -kb, mime file says non-text.
1689 # trunk/foo.bin: has -kb, mime file says nothing.
1690 # trunk/foo.csv: has -kb, mime file says text.
1691 # trunk/foo.dbf: has -kb, mime file says non-text.
1693 ## Neither --no-default-eol nor --eol-from-mime-type. ##
1694 conv = ensure_conversion(
1695 'eol-mime', args=['--mime-types=%s' % mime_path, '--cvs-revnums'])
1696 conv.check_props(
1697 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1699 ('trunk/foo.txt', ['native', None, '1.2']),
1700 ('trunk/foo.xml', ['native', 'text/xml', '1.2']),
1701 ('trunk/foo.zip', ['native', 'application/zip', '1.2']),
1702 ('trunk/foo.bin', [None, 'application/octet-stream', '1.2']),
1703 ('trunk/foo.csv', [None, 'text/csv', '1.2']),
1704 ('trunk/foo.dbf', [None, 'application/what-is-dbf', '1.2']),
1708 ## Just --no-default-eol, not --eol-from-mime-type. ##
1709 conv = ensure_conversion(
1710 'eol-mime', args=['--mime-types=%s' % mime_path, '--no-default-eol'])
1711 conv.check_props(
1712 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1714 ('trunk/foo.txt', [None, None, None]),
1715 ('trunk/foo.xml', [None, 'text/xml', None]),
1716 ('trunk/foo.zip', [None, 'application/zip', None]),
1717 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
1718 ('trunk/foo.csv', [None, 'text/csv', None]),
1719 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
1723 ## Just --eol-from-mime-type, not --no-default-eol. ##
1724 conv = ensure_conversion('eol-mime', args=[
1725 '--mime-types=%s' % mime_path, '--eol-from-mime-type', '--cvs-revnums'
1727 conv.check_props(
1728 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1730 ('trunk/foo.txt', ['native', None, '1.2']),
1731 ('trunk/foo.xml', ['native', 'text/xml', '1.2']),
1732 ('trunk/foo.zip', [None, 'application/zip', '1.2']),
1733 ('trunk/foo.bin', [None, 'application/octet-stream', '1.2']),
1734 ('trunk/foo.csv', [None, 'text/csv', '1.2']),
1735 ('trunk/foo.dbf', [None, 'application/what-is-dbf', '1.2']),
1739 ## Both --no-default-eol and --eol-from-mime-type. ##
1740 conv = ensure_conversion('eol-mime', args=[
1741 '--mime-types=%s' % mime_path, '--eol-from-mime-type',
1742 '--no-default-eol'])
1743 conv.check_props(
1744 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1746 ('trunk/foo.txt', [None, None, None]),
1747 ('trunk/foo.xml', ['native', 'text/xml', None]),
1748 ('trunk/foo.zip', [None, 'application/zip', None]),
1749 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
1750 ('trunk/foo.csv', [None, 'text/csv', None]),
1751 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
1756 def keywords():
1757 "test setting of svn:keywords property among others"
1758 conv = ensure_conversion('keywords')
1759 conv.check_props(
1760 ['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
1762 ('trunk/foo.default', ['Author Date Id Revision', 'native', None]),
1763 ('trunk/foo.kkvl', ['Author Date Id Revision', 'native', None]),
1764 ('trunk/foo.kkv', ['Author Date Id Revision', 'native', None]),
1765 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
1766 ('trunk/foo.kk', [None, 'native', None]),
1767 ('trunk/foo.ko', [None, 'native', None]),
1768 ('trunk/foo.kv', [None, 'native', None]),
1773 def ignore():
1774 "test setting of svn:ignore property"
1775 conv = ensure_conversion('cvsignore')
1776 wc_tree = conv.get_wc_tree()
1777 topdir_props = props_for_path(wc_tree, 'trunk/proj')
1778 subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir')
1780 if topdir_props['svn:ignore'] != \
1781 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1782 raise svntest.Failure
1784 if subdir_props['svn:ignore'] != \
1785 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1786 raise svntest.Failure
1789 def requires_cvs():
1790 "test that CVS can still do what RCS can't"
1791 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
1792 conv = ensure_conversion('requires-cvs', args=["--use-cvs"])
1794 atsign_contents = file(
1795 os.path.join(conv.get_wc(), "trunk", "atsign-add")).read()
1796 cl_contents = file(
1797 os.path.join(conv.get_wc(), "trunk", "client_lock.idl")).read()
1799 if atsign_contents[-1:] == "@":
1800 raise svntest.Failure
1801 if cl_contents.find("gregh\n//\n//Integration for locks") < 0:
1802 raise svntest.Failure
1804 if not (conv.logs[21].author == "William Lyon Phelps III" and
1805 conv.logs[20].author == "j random"):
1806 raise svntest.Failure
1809 def questionable_branch_names():
1810 "test that we can handle weird branch names"
1811 conv = ensure_conversion('questionable-symbols')
1812 # If the conversion succeeds, then we're okay. We could check the
1813 # actual branch paths, too, but the main thing is to know that the
1814 # conversion doesn't fail.
1817 def questionable_tag_names():
1818 "test that we can handle weird tag names"
1819 conv = ensure_conversion('questionable-symbols')
1820 for tag_name in ['Tag_A', 'TagWith--Backslash_E', 'TagWith++Slash_Z']:
1821 conv.find_tag_log(tag_name).check(sym_log_msg(tag_name,1), (
1822 ('/%(tags)s/' + tag_name + ' (from /trunk:8)', 'A'),
1826 def revision_reorder_bug():
1827 "reveal a bug that reorders file revisions"
1828 conv = ensure_conversion('revision-reorder-bug')
1829 # If the conversion succeeds, then we're okay. We could check the
1830 # actual revisions, too, but the main thing is to know that the
1831 # conversion doesn't fail.
1834 def exclude():
1835 "test that exclude really excludes everything"
1836 conv = ensure_conversion('main', args=['--exclude=.*'])
1837 for log in conv.logs.values():
1838 for item in log.changed_paths.keys():
1839 if item.startswith('/branches/') or item.startswith('/tags/'):
1840 raise svntest.Failure
1843 def vendor_branch_delete_add():
1844 "add trunk file that was deleted on vendor branch"
1845 # This will error if the bug is present
1846 conv = ensure_conversion('vendor-branch-delete-add')
1849 def resync_pass2_pull_forward():
1850 "ensure pass2 doesn't pull rev too far forward"
1851 conv = ensure_conversion('resync-pass2-pull-forward')
1852 # If the conversion succeeds, then we're okay. We could check the
1853 # actual revisions, too, but the main thing is to know that the
1854 # conversion doesn't fail.
1857 def native_eol():
1858 "only LFs for svn:eol-style=native files"
1859 conv = ensure_conversion('native-eol')
1860 lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q',
1861 conv.repos)
1862 # Verify that all files in the dump have LF EOLs. We're actually
1863 # testing the whole dump file, but the dump file itself only uses
1864 # LF EOLs, so we're safe.
1865 for line in lines:
1866 if line[-1] != '\n' or line[:-1].find('\r') != -1:
1867 raise svntest.Failure
1870 def double_fill():
1871 "reveal a bug that created a branch twice"
1872 conv = ensure_conversion('double-fill')
1873 # If the conversion succeeds, then we're okay. We could check the
1874 # actual revisions, too, but the main thing is to know that the
1875 # conversion doesn't fail.
1878 def resync_pass2_push_backward():
1879 "ensure pass2 doesn't push rev too far backward"
1880 conv = ensure_conversion('resync-pass2-push-backward')
1881 # If the conversion succeeds, then we're okay. We could check the
1882 # actual revisions, too, but the main thing is to know that the
1883 # conversion doesn't fail.
1886 def double_add():
1887 "reveal a bug that added a branch file twice"
1888 conv = ensure_conversion('double-add')
1889 # If the conversion succeeds, then we're okay. We could check the
1890 # actual revisions, too, but the main thing is to know that the
1891 # conversion doesn't fail.
1894 def bogus_branch_copy():
1895 "reveal a bug that copies a branch file wrongly"
1896 conv = ensure_conversion('bogus-branch-copy')
1897 # If the conversion succeeds, then we're okay. We could check the
1898 # actual revisions, too, but the main thing is to know that the
1899 # conversion doesn't fail.
1902 def nested_ttb_directories():
1903 "require error if ttb directories are not disjoint"
1904 opts_list = [
1905 {'trunk' : 'a', 'branches' : 'a',},
1906 {'trunk' : 'a', 'tags' : 'a',},
1907 {'branches' : 'a', 'tags' : 'a',},
1908 # This option conflicts with the default trunk path:
1909 {'branches' : 'trunk',},
1910 # Try some nested directories:
1911 {'trunk' : 'a', 'branches' : 'a/b',},
1912 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
1913 {'branches' : 'a', 'tags' : 'a/b',},
1916 for opts in opts_list:
1917 try:
1918 ensure_conversion(
1919 'main', error_re=r'.*paths .* and .* are not disjoint\.', **opts
1921 raise MissingErrorException
1922 except svntest.Failure:
1923 pass
1926 #----------------------------------------------------------------------
1928 ########################################################################
1929 # Run the tests
1931 # list all tests here, starting with None:
1932 test_list = [ None,
1933 show_usage, # 1
1934 attr_exec,
1935 space_fname,
1936 two_quick,
1937 prune_with_care,
1938 interleaved_commits,
1939 simple_commits,
1940 simple_tags,
1941 simple_branch_commits,
1942 mixed_time_tag, # 10
1943 mixed_time_branch_with_added_file,
1944 mixed_commit,
1945 split_time_branch,
1946 bogus_tag,
1947 overlapping_branch,
1948 phoenix_branch,
1949 ctrl_char_in_log,
1950 overdead,
1951 no_trunk_prune,
1952 double_delete, # 20
1953 split_branch,
1954 resync_misgroups,
1955 tagged_branch_and_trunk,
1956 enroot_race,
1957 enroot_race_obo,
1958 branch_delete_first,
1959 nonascii_filenames,
1960 vendor_branch_sameness,
1961 default_branches,
1962 compose_tag_three_sources, # 30
1963 pass5_when_to_fill,
1964 peer_path_pruning,
1965 empty_trunk,
1966 no_spurious_svn_commits,
1967 invalid_closings_on_trunk,
1968 individual_passes,
1969 resync_bug,
1970 branch_from_default_branch,
1971 file_in_attic_too,
1972 symbolic_name_filling_guide, # 40
1973 eol_mime,
1974 keywords,
1975 ignore,
1976 requires_cvs,
1977 questionable_branch_names,
1978 questionable_tag_names,
1979 revision_reorder_bug,
1980 exclude,
1981 vendor_branch_delete_add,
1982 resync_pass2_pull_forward, # 50
1983 native_eol,
1984 double_fill,
1985 resync_pass2_push_backward,
1986 double_add,
1987 bogus_branch_copy,
1988 nested_ttb_directories,
1989 prune_with_care_variants,
1990 simple_tags_variants,
1991 phoenix_branch_variants,
1992 no_trunk_prune_variants, # 60
1993 tagged_branch_and_trunk_variants,
1994 branch_delete_first_variants,
1995 empty_trunk_variants,
1996 peer_path_pruning_variants,
1999 if __name__ == '__main__':
2001 # The Subversion test suite code assumes it's being invoked from
2002 # within a working copy of the Subversion sources, and tries to use
2003 # the binaries in that tree. Since the cvs2svn tree never contains
2004 # a Subversion build, we just use the system's installed binaries.
2005 svntest.main.svn_binary = 'svn'
2006 svntest.main.svnlook_binary = 'svnlook'
2007 svntest.main.svnadmin_binary = 'svnadmin'
2008 svntest.main.svnversion_binary = 'svnversion'
2010 svntest.main.run_tests(test_list)
2011 # NOTREACHED
2014 ### End of file.