3 # run_tests.py: test suite for cvs2svn
5 # Usage: run_tests.py [-v | --verbose] [list | <num>]
9 # enable verbose output
11 # Arguments (at most one argument is allowed):
13 # If the word "list" is passed as an argument, the list of
14 # available tests is printed (but no tests are run).
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 ######################################################################
47 # Make sure this Python is recent enough.
48 if sys
.hexversion
< 0x02020000:
49 sys
.stderr
.write("error: Python 2.2 or higher required, "
50 "see www.python.org.\n")
53 # This script needs to run in the correct directory. Make sure we're there.
54 if not (os
.path
.exists('cvs2svn') and os
.path
.exists('test-data')):
55 sys
.stderr
.write("error: I need to be run in the directory containing "
56 "'cvs2svn' and 'test-data'.\n")
59 # Load the Subversion test framework.
63 Skip
= svntest
.testcase
.Skip
64 XFail
= svntest
.testcase
.XFail
66 cvs2svn
= os
.path
.abspath('cvs2svn')
68 # We use the installed svn and svnlook binaries, instead of using
69 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
70 # behavior -- or even existence -- of local builds shouldn't affect
71 # the cvs2svn test suite.
75 test_data_dir
= 'test-data'
79 #----------------------------------------------------------------------
81 #----------------------------------------------------------------------
84 class RunProgramException
:
87 class MissingErrorException
:
90 def run_program(program
, error_re
, *varargs
):
91 """Run PROGRAM with VARARGS, return stdout as a list of lines.
92 If there is any stderr and ERROR_RE is None, raise
93 RunProgramException, and print the stderr lines if
94 svntest.main.verbose_mode is true.
96 If ERROR_RE is not None, it is a string regular expression that must
97 match some line of stderr. If it fails to match, raise
98 MissingErrorExpection."""
99 out
, err
= svntest
.main
.run_command(program
, 1, 0, *varargs
)
103 if re
.match(error_re
, line
):
105 raise MissingErrorException
107 if svntest
.main
.verbose_mode
:
108 print '\n%s said:\n' % program
112 raise RunProgramException
116 def run_cvs2svn(error_re
, *varargs
):
117 """Run cvs2svn with VARARGS, return stdout as a list of lines.
118 If there is any stderr and ERROR_RE is None, raise
119 RunProgramException, and print the stderr lines if
120 svntest.main.verbose_mode is true.
122 If ERROR_RE is not None, it is a string regular expression that must
123 match some line of stderr. If it fails to match, raise
124 MissingErrorException."""
125 # Use the same python that is running this script
126 return run_program(sys
.executable
, error_re
, cvs2svn
, *varargs
)
127 # On Windows, for an unknown reason, the cmd.exe process invoked by
128 # os.system('sort ...') in cvs2svn receives invalid stdio handles, if
129 # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids
130 # this. Therefore, the redirection of the output to the .s-revs file fails.
131 # We no longer use the problematic invocation on any system, but this
132 # comment remains to warn about this problem.
135 def run_svn(*varargs
):
136 """Run svn with VARARGS; return stdout as a list of lines.
137 If there is any stderr, raise RunProgramException, and print the
138 stderr lines if svntest.main.verbose_mode is true."""
139 return run_program(svn
, None, *varargs
)
142 def repos_to_url(path_to_svn_repos
):
143 """This does what you think it does."""
144 rpath
= os
.path
.abspath(path_to_svn_repos
)
147 return 'file://%s' % string
.replace(rpath
, os
.sep
, '/')
149 if hasattr(time
, 'strptime'):
150 def svn_strptime(timestr
):
151 return time
.strptime(timestr
, '%Y-%m-%d %H:%M:%S')
153 # This is for Python earlier than 2.3 on Windows
154 _re_rev_date
= re
.compile(r
'(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)')
155 def svn_strptime(timestr
):
156 matches
= _re_rev_date
.match(timestr
).groups()
157 return tuple(map(int, matches
)) + (0, 1, -1)
160 def __init__(self
, revision
, author
, date
, symbols
):
161 self
.revision
= revision
164 # Internally, we represent the date as seconds since epoch (UTC).
165 # Since standard subversion log output shows dates in localtime
167 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
169 # and time.mktime() converts from localtime, it all works out very
171 self
.date
= time
.mktime(svn_strptime(date
[0:19]))
173 # The following symbols are used for string interpolation when
175 self
.symbols
= symbols
177 # The changed paths will be accumulated later, as log data is read.
178 # Keys here are paths such as '/trunk/foo/bar', values are letter
179 # codes such as 'M', 'A', and 'D'.
180 self
.changed_paths
= { }
182 # The msg will be accumulated later, as log data is read.
185 def absorb_changed_paths(self
, out
):
186 'Read changed paths from OUT into self, until no more.'
188 line
= out
.readline()
189 if len(line
) == 1: return
191 op_portion
= line
[3:4]
192 path_portion
= line
[5:]
193 # If we're running on Windows we get backslashes instead of
195 path_portion
= path_portion
.replace('\\', '/')
196 # # We could parse out history information, but currently we
197 # # just leave it in the path portion because that's how some
200 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
202 # path_portion = m.group(1)
203 self
.changed_paths
[path_portion
] = op_portion
205 def __cmp__(self
, other
):
206 return cmp(self
.revision
, other
.revision
) or \
207 cmp(self
.author
, other
.author
) or cmp(self
.date
, other
.date
) or \
208 cmp(self
.changed_paths
, other
.changed_paths
) or \
209 cmp(self
.msg
, other
.msg
)
211 def get_path_op(self
, path
):
212 """Return the operator for the change involving PATH.
214 PATH is allowed to include string interpolation directives (e.g.,
215 '%(trunk)s'), which are interpolated against self.symbols. Return
216 None if there is no record for PATH."""
217 return self
.changed_paths
.get(path
% self
.symbols
)
219 def check_msg(self
, msg
):
220 """Verify that this Log's message starts with the specified MSG."""
221 if self
.msg
.find(msg
) != 0:
222 raise svntest
.Failure(
223 "Revision %d log message was:\n%s\n\n"
224 "It should have begun with:\n%s\n\n"
225 % (self
.revision
, self
.msg
, msg
,)
228 def check_change(self
, path
, op
):
229 """Verify that this Log includes a change for PATH with operator OP.
231 PATH is allowed to include string interpolation directives (e.g.,
232 '%(trunk)s'), which are interpolated against self.symbols."""
234 path
= path
% self
.symbols
235 found_op
= self
.changed_paths
.get(path
, None)
237 raise svntest
.Failure(
238 "Revision %d does not include change for path %s "
239 "(it should have been %s).\n"
240 % (self
.revision
, path
, op
,)
243 raise svntest
.Failure(
244 "Revision %d path %s had op %s (it should have been %s)\n"
245 % (self
.revision
, path
, found_op
, op
,)
248 def check_changes(self
, changed_paths
):
249 """Verify that this Log has precisely the CHANGED_PATHS specified.
251 CHANGED_PATHS is a sequence of tuples (path, op), where the paths
252 strings are allowed to include string interpolation directives
253 (e.g., '%(trunk)s'), which are interpolated against self.symbols."""
256 for (path
, op
) in changed_paths
:
257 cp
[path
% self
.symbols
] = op
259 if self
.changed_paths
!= cp
:
260 raise svntest
.Failure(
261 "Revision %d changed paths list was:\n%s\n\n"
262 "It should have been:\n%s\n\n"
263 % (self
.revision
, self
.changed_paths
, cp
,)
266 def check(self
, msg
, changed_paths
):
267 """Verify that this Log has the MSG and CHANGED_PATHS specified.
269 Convenience function to check two things at once. MSG is passed
270 to check_msg(); CHANGED_PATHS is passed to check_changes()."""
273 self
.check_changes(changed_paths
)
276 def parse_log(svn_repos
, symbols
):
277 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS.
279 Initialize the Logs' symbols with SYMBOLS."""
282 'Make a list of lines behave like an open file handle.'
283 def __init__(self
, lines
):
286 if len(self
.lines
) > 0:
287 return self
.lines
.pop(0)
291 def absorb_message_body(out
, num_lines
, log
):
292 'Read NUM_LINES of log message body from OUT into Log item LOG.'
295 line
= out
.readline()
299 log_start_re
= re
.compile('^r(?P<rev>[0-9]+) \| '
300 '(?P<author>[^\|]+) \| '
302 '\| (?P<lines>[0-9]+) (line|lines)$')
304 log_separator
= '-' * 72
308 out
= LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos
)))
312 line
= out
.readline()
316 if line
.find(log_separator
) == 0:
317 line
= out
.readline()
320 m
= log_start_re
.match(line
)
323 int(m
.group('rev')), m
.group('author'), m
.group('date'), symbols
)
324 line
= out
.readline()
325 if not line
.find('Changed paths:') == 0:
326 print 'unexpected log output (missing changed paths)'
327 print "Line: '%s'" % line
329 this_log
.absorb_changed_paths(out
)
330 absorb_message_body(out
, int(m
.group('lines')), this_log
)
331 logs
[this_log
.revision
] = this_log
333 break # We've reached the end of the log output.
335 print 'unexpected log output (missing revision line)'
336 print "Line: '%s'" % line
339 print 'unexpected log output (missing log separator)'
340 print "Line: '%s'" % line
347 """Unconditionally remove PATH and its subtree, if any. PATH may be
348 non-existent, a file or symlink, or a directory."""
349 if os
.path
.isdir(path
):
350 svntest
.main
.safe_rmtree(path
)
351 elif os
.path
.exists(path
):
355 def sym_log_msg(symbolic_name
, is_tag
=None):
356 """Return the expected log message for a cvs2svn-synthesized revision
357 creating branch or tag SYMBOLIC_NAME."""
358 # This is a copy-paste of part of cvs2svn's make_revision_props
364 # In Python 2.2.3, we could use textwrap.fill(). Oh well :-).
365 if len(symbolic_name
) >= 13:
366 space_or_newline
= '\n'
368 space_or_newline
= ' '
370 log
= "This commit was manufactured by cvs2svn to create %s%s'%s'." \
371 % (type, space_or_newline
, symbolic_name
)
376 def make_conversion_id(name
, args
, passbypass
):
377 """Create an identifying tag for a conversion.
379 The return value can also be used as part of a filesystem path.
381 NAME is the name of the CVS repository.
383 ARGS are the extra arguments to be passed to cvs2svn.
385 PASSBYPASS is a boolean indicating whether the conversion is to be
386 run one pass at a time.
388 The 1-to-1 mapping between cvs2svn command parameters and
389 conversion_ids allows us to avoid running the same conversion more
390 than once, when multiple tests use exactly the same conversion."""
394 _win32_fname_mapping
= { '/': '_sl_', '\\': '_bs_', ':': '_co_',
395 '*': '_st_', '?': '_qm_', '"': '_qq_',
396 '<': '_lt_', '>': '_gt_', '|': '_pi_', }
398 # Replace some characters that Win32 isn't happy about having in a
399 # filename (which was causing the eol_mime test to fail).
401 for a
, b
in _win32_fname_mapping
.items():
402 sanitized_arg
= sanitized_arg
.replace(a
, b
)
403 conv_id
= conv_id
+ sanitized_arg
406 conv_id
= conv_id
+ '-passbypass'
412 """A record of a cvs2svn conversion.
416 conv_id -- the conversion id for this Conversion.
418 name -- a one-word name indicating the involved repositories.
420 repos -- the path to the svn repository.
422 logs -- a dictionary of Log instances, as returned by parse_log().
424 symbols -- a dictionary of symbols used for string interpolation
427 _wc -- the basename of the svn working copy (within tmp_dir).
429 _wc_path -- the path to the svn working copy, if it has already
430 been created; otherwise, None. (The working copy is created
431 lazily when get_wc() is called.)
433 _wc_tree -- the tree built from the svn working copy, if it has
434 already been created; otherwise, None. The tree is created
435 lazily when get_wc_tree() is called.)
437 _svnrepos -- the basename of the svn repository (within tmp_dir)."""
439 def __init__(self
, conv_id
, name
, error_re
, passbypass
, symbols
, args
):
440 self
.conv_id
= conv_id
442 self
.symbols
= symbols
443 if not os
.path
.isdir(tmp_dir
):
446 cvsrepos
= os
.path
.join('..', test_data_dir
, '%s-cvsrepos' % self
.name
)
448 saved_wd
= os
.getcwd()
452 self
._svnrepos
= '%s-svnrepos' % self
.conv_id
453 self
.repos
= os
.path
.join(tmp_dir
, self
._svnrepos
)
454 self
._wc
= '%s-wc' % self
.conv_id
458 # Clean up from any previous invocations of this script.
459 erase(self
._svnrepos
)
463 args
.extend( [ '--bdb-txn-nosync', '-s', self
._svnrepos
, cvsrepos
] )
465 for p
in range(1, 9):
466 run_cvs2svn(error_re
, '-p', str(p
), *args
)
468 run_cvs2svn(error_re
, *args
)
469 except RunProgramException
:
470 raise svntest
.Failure
471 except MissingErrorException
:
472 raise svntest
.Failure("Test failed because no error matched '%s'"
475 if not os
.path
.isdir(self
._svnrepos
):
476 raise svntest
.Failure("Repository not created: '%s'"
477 % os
.path
.join(os
.getcwd(), self
._svnrepos
))
479 self
.logs
= parse_log(self
._svnrepos
, self
.symbols
)
483 def find_tag_log(self
, tagname
):
484 """Search LOGS for a log message containing 'TAGNAME' and return the
485 log in which it was found."""
486 for i
in xrange(len(self
.logs
), 0, -1):
487 if self
.logs
[i
].msg
.find("'"+tagname
+"'") != -1:
489 raise ValueError("Tag %s not found in logs" % tagname
)
492 """Return the path to the svn working copy. If it has not been
493 created yet, create it now."""
494 if self
._wc
_path
is None:
495 saved_wd
= os
.getcwd()
498 run_svn('co', repos_to_url(self
._svnrepos
), self
._wc
)
499 self
._wc
_path
= os
.path
.join(tmp_dir
, self
._wc
)
504 def get_wc_tree(self
):
505 if self
._wc
_tree
is None:
506 self
._wc
_tree
= svntest
.tree
.build_tree_from_wc(self
.get_wc(), 1)
509 def check_props(self
, keys
, checks
):
510 """Helper function for checking lots of properties. For a list of
511 files in the conversion, check that the values of the properties
512 listed in KEYS agree with those listed in CHECKS. CHECKS is a
513 list of tuples: [ (filename, [value, value, ...]), ...], where the
514 values are listed in the same order as the key names are listed in
517 for (file, values
) in checks
:
518 assert len(values
) == len(keys
)
519 props
= props_for_path(self
.get_wc_tree(), file)
520 for i
in range(len(keys
)):
521 if props
.get(keys
[i
]) != values
[i
]:
522 raise svntest
.Failure(
523 "File %s has property %s set to \"%s\" "
524 "(it should have been \"%s\").\n"
525 % (file, keys
[i
], props
.get(keys
[i
]), values
[i
],)
529 # Cache of conversions that have already been done. Keys are conv_id;
530 # values are Conversion instances.
531 already_converted
= { }
533 def ensure_conversion(name
, error_re
=None, passbypass
=None,
534 trunk
=None, branches
=None, tags
=None, args
=None):
535 """Convert CVS repository NAME to Subversion, but only if it has not
536 been converted before by this invocation of this script. If it has
537 been converted before, return the Conversion object from the
540 If no error, return a Conversion instance.
542 If ERROR_RE is a string, it is a regular expression expected to
543 match some line of stderr printed by the conversion. If there is an
544 error and ERROR_RE is not set, then raise svntest.Failure.
546 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
547 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
549 NAME is just one word. For example, 'main' would mean to convert
550 './test-data/main-cvsrepos', and after the conversion, the resulting
551 Subversion repository would be in './tmp/main-svnrepos', and a
552 checked out head working copy in './tmp/main-wc'.
554 Any other options to pass to cvs2svn should be in ARGS, each element
555 being one option, e.g., '--trunk-only'. If the option takes an
556 argument, include it directly, e.g., '--mime-types=PATH'. Arguments
557 are passed to cvs2svn in the order that they appear in ARGS.
568 args
.append('--trunk=%s' % (trunk
,))
571 branches
= 'branches'
573 args
.append('--branches=%s' % (branches
,))
578 args
.append('--tags=%s' % (tags
,))
580 conv_id
= make_conversion_id(name
, args
, passbypass
)
582 if not already_converted
.has_key(conv_id
):
584 # Run the conversion and store the result for the rest of this
586 already_converted
[conv_id
] = Conversion(
587 conv_id
, name
, error_re
, passbypass
,
588 {'trunk' : trunk
, 'branches' : branches
, 'tags' : tags
},
590 except svntest
.Failure
:
591 # Remember the failure so that a future attempt to run this conversion
592 # does not bother to retry, but fails immediately.
593 already_converted
[conv_id
] = None
596 conv
= already_converted
[conv_id
]
598 raise svntest
.Failure
602 #----------------------------------------------------------------------
604 #----------------------------------------------------------------------
608 "cvs2svn with no arguments shows usage"
609 out
= run_cvs2svn(None)
610 if (len(out
) > 2 and out
[0].find('ERROR:') == 0
611 and out
[1].find('DBM module')):
612 print 'cvs2svn cannot execute due to lack of proper DBM module.'
613 print 'Exiting without running any further tests.'
615 if out
[0].find('USAGE') < 0:
616 raise svntest
.Failure('Basic cvs2svn invocation failed.')
619 def show_help_passes():
620 "cvs2svn --help-passes shows pass information"
621 out
= run_cvs2svn(None, '--help-passes')
622 if out
[0].find('PASSES') < 0:
623 raise svntest
.Failure('cvs2svn --help-passes failed.')
627 "detection of the executable flag"
628 if sys
.platform
== 'win32':
630 conv
= ensure_conversion('main')
632 os
.path
.join(conv
.get_wc(), 'trunk', 'single-files', 'attr-exec'))
633 if not st
[0] & stat
.S_IXUSR
:
634 raise svntest
.Failure
638 "conversion of filename with a space"
639 conv
= ensure_conversion('main')
640 if not os
.path
.exists(
641 os
.path
.join(conv
.get_wc(), 'trunk', 'single-files', 'space fname')):
642 raise svntest
.Failure
646 "two commits in quick succession"
647 conv
= ensure_conversion('main')
649 os
.path
.join(conv
.repos
, 'trunk', 'single-files', 'twoquick'), {})
651 raise svntest
.Failure
654 def prune_with_care(**kw
):
655 "prune, but never too much"
656 # Robert Pluim encountered this lovely one while converting the
657 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
658 # repository (see issue #1302). Step 4 is the doozy:
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: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
664 # revision 5: does nothing
666 # After fixing cvs2svn, the sequence (correctly) looks like this:
668 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
669 # revision 2: adds trunk/blah/NEWS
670 # revision 3: deletes trunk/blah/cookie
671 # revision 4: does nothing [because trunk/blah/cookie already deleted]
672 # revision 5: deletes blah
674 # The difference is in 4 and 5. In revision 4, it's not correct to
675 # prune blah/, because NEWS is still in there, so revision 4 does
676 # nothing now. But when we delete NEWS in 5, that should bubble up
677 # and prune blah/ instead.
679 # ### Note that empty revisions like 4 are probably going to become
680 # ### at least optional, if not banished entirely from cvs2svn's
681 # ### output. Hmmm, or they may stick around, with an extra
682 # ### revision property explaining what happened. Need to think
683 # ### about that. In some sense, it's a bug in Subversion itself,
684 # ### that such revisions don't show up in 'svn log' output.
686 # In the test below, 'trunk/full-prune/first' represents
687 # cookie, and 'trunk/full-prune/second' represents NEWS.
689 conv
= ensure_conversion('main', **kw
)
691 # Confirm that revision 4 removes '/trunk/full-prune/first',
692 # and that revision 6 removes '/trunk/full-prune'.
694 # Also confirm similar things about '/full-prune-reappear/...',
695 # which is similar, except that later on it reappears, restored
696 # from pruneland, because a file gets added to it.
698 # And finally, a similar thing for '/partial-prune/...', except that
699 # in its case, a permanent file on the top level prevents the
700 # pruning from going farther than the subdirectory containing first
704 for path
in ('/%(trunk)s/full-prune/first',
705 '/%(trunk)s/full-prune-reappear/sub/first',
706 '/%(trunk)s/partial-prune/sub/first'):
707 conv
.logs
[rev
].check_change(path
, 'D')
710 for path
in ('/%(trunk)s/full-prune',
711 '/%(trunk)s/full-prune-reappear',
712 '/%(trunk)s/partial-prune/sub'):
713 conv
.logs
[rev
].check_change(path
, 'D')
716 for path
in ('/%(trunk)s/full-prune-reappear',
717 '/%(trunk)s/full-prune-reappear/appears-later'):
718 conv
.logs
[rev
].check_change(path
, 'A')
721 def prune_with_care_variants():
722 "prune, with alternate repo layout"
723 prune_with_care(trunk
='a', branches
='b', tags
='c')
724 prune_with_care(trunk
='a/1', branches
='b/1', tags
='c/1')
725 prune_with_care(trunk
='a/1', branches
='a/2', tags
='a/3')
728 def interleaved_commits():
729 "two interleaved trunk commits, different log msgs"
730 # See test-data/main-cvsrepos/proj/README.
731 conv
= ensure_conversion('main')
733 # The initial import.
735 conv
.logs
[rev
].check('Initial revision', (
736 ('/%(trunk)s/interleaved', 'A'),
737 ('/%(trunk)s/interleaved/1', 'A'),
738 ('/%(trunk)s/interleaved/2', 'A'),
739 ('/%(trunk)s/interleaved/3', 'A'),
740 ('/%(trunk)s/interleaved/4', 'A'),
741 ('/%(trunk)s/interleaved/5', 'A'),
742 ('/%(trunk)s/interleaved/a', 'A'),
743 ('/%(trunk)s/interleaved/b', 'A'),
744 ('/%(trunk)s/interleaved/c', 'A'),
745 ('/%(trunk)s/interleaved/d', 'A'),
746 ('/%(trunk)s/interleaved/e', 'A'),
749 # This PEP explains why we pass the 'log' parameter to these two
750 # nested functions, instead of just inheriting it from the enclosing
751 # scope: http://www.python.org/peps/pep-0227.html
753 def check_letters(log
):
754 """Check if REV is the rev where only letters were committed."""
755 log
.check('Committing letters only.', (
756 ('/%(trunk)s/interleaved/a', 'M'),
757 ('/%(trunk)s/interleaved/b', 'M'),
758 ('/%(trunk)s/interleaved/c', 'M'),
759 ('/%(trunk)s/interleaved/d', 'M'),
760 ('/%(trunk)s/interleaved/e', 'M'),
763 def check_numbers(log
):
764 """Check if REV is the rev where only numbers were committed."""
765 log
.check('Committing numbers only.', (
766 ('/%(trunk)s/interleaved/1', 'M'),
767 ('/%(trunk)s/interleaved/2', 'M'),
768 ('/%(trunk)s/interleaved/3', 'M'),
769 ('/%(trunk)s/interleaved/4', 'M'),
770 ('/%(trunk)s/interleaved/5', 'M'),
773 # One of the commits was letters only, the other was numbers only.
774 # But they happened "simultaneously", so we don't assume anything
775 # about which commit appeared first, so we just try both ways.
778 check_letters(conv
.logs
[rev
])
779 check_numbers(conv
.logs
[rev
+ 1])
780 except svntest
.Failure
:
781 check_numbers(conv
.logs
[rev
])
782 check_letters(conv
.logs
[rev
+ 1])
785 def simple_commits():
786 "simple trunk commits"
787 # See test-data/main-cvsrepos/proj/README.
788 conv
= ensure_conversion('main')
790 # The initial import.
792 conv
.logs
[rev
].check('Initial revision', (
793 ('/%(trunk)s/proj', 'A'),
794 ('/%(trunk)s/proj/default', 'A'),
795 ('/%(trunk)s/proj/sub1', 'A'),
796 ('/%(trunk)s/proj/sub1/default', 'A'),
797 ('/%(trunk)s/proj/sub1/subsubA', 'A'),
798 ('/%(trunk)s/proj/sub1/subsubA/default', 'A'),
799 ('/%(trunk)s/proj/sub1/subsubB', 'A'),
800 ('/%(trunk)s/proj/sub1/subsubB/default', 'A'),
801 ('/%(trunk)s/proj/sub2', 'A'),
802 ('/%(trunk)s/proj/sub2/default', 'A'),
803 ('/%(trunk)s/proj/sub2/subsubA', 'A'),
804 ('/%(trunk)s/proj/sub2/subsubA/default', 'A'),
805 ('/%(trunk)s/proj/sub3', 'A'),
806 ('/%(trunk)s/proj/sub3/default', 'A'),
811 conv
.logs
[rev
].check('First commit to proj, affecting two files.', (
812 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
813 ('/%(trunk)s/proj/sub3/default', 'M'),
818 conv
.logs
[rev
].check('Second commit to proj, affecting all 7 files.', (
819 ('/%(trunk)s/proj/default', 'M'),
820 ('/%(trunk)s/proj/sub1/default', 'M'),
821 ('/%(trunk)s/proj/sub1/subsubA/default', 'M'),
822 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
823 ('/%(trunk)s/proj/sub2/default', 'M'),
824 ('/%(trunk)s/proj/sub2/subsubA/default', 'M'),
825 ('/%(trunk)s/proj/sub3/default', 'M')
829 def simple_tags(**kw
):
830 "simple tags and branches with no commits"
831 # See test-data/main-cvsrepos/proj/README.
832 conv
= ensure_conversion('main', **kw
)
834 # Verify the copy source for the tags we are about to check
835 # No need to verify the copyfrom revision, as simple_commits did that
836 conv
.logs
[24].check(sym_log_msg('vendorbranch'), (
837 ('/%(branches)s/vendorbranch/proj (from /%(trunk)s/proj:23)', 'A'),
840 fromstr
= ' (from /%(branches)s/vendorbranch:25)'
842 # Tag on rev 1.1.1.1 of all files in proj
843 log
= conv
.find_tag_log('T_ALL_INITIAL_FILES')
844 log
.check(sym_log_msg('T_ALL_INITIAL_FILES',1), (
845 ('/%(tags)s/T_ALL_INITIAL_FILES'+fromstr
, 'A'),
846 ('/%(tags)s/T_ALL_INITIAL_FILES/single-files', 'D'),
847 ('/%(tags)s/T_ALL_INITIAL_FILES/partial-prune', 'D'),
850 # The same, as a branch
851 conv
.logs
[26].check(sym_log_msg('B_FROM_INITIALS'), (
852 ('/%(branches)s/B_FROM_INITIALS'+fromstr
, 'A'),
853 ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'),
854 ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'),
857 # Tag on rev 1.1.1.1 of all files in proj, except one
858 log
= conv
.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE')
859 log
.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), (
860 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr
, 'A'),
861 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/single-files', 'D'),
862 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune', 'D'),
863 ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'),
866 # The same, as a branch
867 conv
.logs
[27].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), (
868 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE'+fromstr
, 'A'),
869 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'),
870 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'),
871 ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'),
875 def simple_tags_variants():
876 "simple tags, with alternate repo layout"
877 simple_tags(trunk
='a', branches
='b', tags
='c')
878 simple_tags(trunk
='a/1', branches
='b/1', tags
='c/1')
879 simple_tags(trunk
='a/1', branches
='a/2', tags
='a/3')
882 def simple_branch_commits():
883 "simple branch commits"
884 # See test-data/main-cvsrepos/proj/README.
885 conv
= ensure_conversion('main')
888 conv
.logs
[rev
].check('Modify three files, on branch B_MIXED.', (
889 ('/%(branches)s/B_MIXED/proj/default', 'M'),
890 ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'),
891 ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'),
895 def mixed_time_tag():
897 # See test-data/main-cvsrepos/proj/README.
898 conv
= ensure_conversion('main')
900 log
= conv
.find_tag_log('T_MIXED')
902 ('/%(tags)s/T_MIXED (from /%(trunk)s:31)', 'A'),
903 ('/%(tags)s/T_MIXED/partial-prune', 'D'),
904 ('/%(tags)s/T_MIXED/single-files', 'D'),
905 ('/%(tags)s/T_MIXED/proj/sub2/subsubA '
906 '(from /%(trunk)s/proj/sub2/subsubA:23)', 'R'),
907 ('/%(tags)s/T_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:30)', 'R'),
909 if log
.revision
== 16:
910 expected
.append(('/%(tags)s', 'A'))
911 log
.check_changes(expected
)
914 def mixed_time_branch_with_added_file():
915 "mixed-time branch, and a file added to the branch"
916 # See test-data/main-cvsrepos/proj/README.
917 conv
= ensure_conversion('main')
919 # A branch from the same place as T_MIXED in the previous test,
920 # plus a file added directly to the branch
921 conv
.logs
[32].check(sym_log_msg('B_MIXED'), (
922 ('/%(branches)s/B_MIXED (from /%(trunk)s:31)', 'A'),
923 ('/%(branches)s/B_MIXED/partial-prune', 'D'),
924 ('/%(branches)s/B_MIXED/single-files', 'D'),
925 ('/%(branches)s/B_MIXED/proj/sub2/subsubA '
926 '(from /%(trunk)s/proj/sub2/subsubA:23)', 'R'),
927 ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:30)', 'R'),
930 conv
.logs
[34].check('Add a file on branch B_MIXED.', (
931 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'),
936 "a commit affecting both trunk and a branch"
937 # See test-data/main-cvsrepos/proj/README.
938 conv
= ensure_conversion('main')
941 'A single commit affecting one file on branch B_MIXED '
942 'and one on trunk.', (
943 ('/%(trunk)s/proj/sub2/default', 'M'),
944 ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'),
948 def split_time_branch():
949 "branch some trunk files, and later branch the rest"
950 # See test-data/main-cvsrepos/proj/README.
951 conv
= ensure_conversion('main')
954 # First change on the branch, creating it
955 conv
.logs
[rev
].check(sym_log_msg('B_SPLIT'), (
956 ('/%(branches)s/B_SPLIT (from /%(trunk)s:36)', 'A'),
957 ('/%(branches)s/B_SPLIT/partial-prune', 'D'),
958 ('/%(branches)s/B_SPLIT/single-files', 'D'),
959 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'),
962 conv
.logs
[rev
+ 1].check('First change on branch B_SPLIT.', (
963 ('/%(branches)s/B_SPLIT/proj/default', 'M'),
964 ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'),
965 ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'),
966 ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'),
967 ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'),
970 # A trunk commit for the file which was not branched
971 conv
.logs
[rev
+ 2].check('A trunk change to sub1/subsubB/default. '
972 'This was committed about an', (
973 ('/%(trunk)s/proj/sub1/subsubB/default', 'M'),
976 # Add the file not already branched to the branch, with modification:w
977 conv
.logs
[rev
+ 3].check(sym_log_msg('B_SPLIT'), (
978 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB '
979 '(from /%(trunk)s/proj/sub1/subsubB:44)', 'A'),
982 conv
.logs
[rev
+ 4].check('This change affects sub3/default and '
983 'sub1/subsubB/default, on branch', (
984 ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'),
985 ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'),
990 "conversion of invalid symbolic names"
991 conv
= ensure_conversion('bogus-tag')
994 def overlapping_branch():
995 "ignore a file with a branch with two names"
996 conv
= ensure_conversion('overlapping-branch',
997 error_re
='.*cannot also have name \'vendorB\'')
999 conv
.logs
[rev
].check_change('/%(branches)s/vendorA (from /%(trunk)s:3)',
1001 # We don't know what order the first two commits would be in, since
1002 # they have different log messages but the same timestamps. As only
1003 # one of the files would be on the vendorB branch in the regression
1004 # case being tested here, we allow for either order.
1005 if (conv
.logs
[rev
].get_path_op(
1006 '/%(branches)s/vendorB (from /%(trunk)s:2)') == 'A'
1007 or conv
.logs
[rev
].get_path_op(
1008 '/%(branches)s/vendorB (from /%(trunk)s:3)') == 'A'):
1009 raise svntest
.Failure
1010 conv
.logs
[rev
+ 1].check_changes(())
1011 if len(conv
.logs
) != rev
+ 1:
1012 raise svntest
.Failure
1015 def phoenix_branch(**kw
):
1016 "convert a branch file rooted in a 'dead' revision"
1017 conv
= ensure_conversion('phoenix', **kw
)
1018 conv
.logs
[8].check(sym_log_msg('volsung_20010721'), (
1019 ('/%(branches)s/volsung_20010721 (from /%(trunk)s:7)', 'A'),
1020 ('/%(branches)s/volsung_20010721/file.txt', 'D'),
1022 conv
.logs
[9].check('This file was supplied by Jack Moffitt', (
1023 ('/%(branches)s/volsung_20010721/phoenix', 'A'),
1027 def phoenix_branch_variants():
1028 "'dead' revision, with alternate repo layout"
1029 phoenix_branch(trunk
='a/1', branches
='b/1', tags
='c/1')
1032 ###TODO: We check for 4 changed paths here to accomodate creating tags
1033 ###and branches in rev 1, but that will change, so this will
1034 ###eventually change back.
1035 def ctrl_char_in_log():
1036 "handle a control char in a log message"
1037 # This was issue #1106.
1039 conv
= ensure_conversion('ctrl-char-in-log')
1040 conv
.logs
[rev
].check_changes((
1041 ('/%(trunk)s/ctrl-char-in-log', 'A'),
1043 if conv
.logs
[rev
].msg
.find('\x04') < 0:
1044 raise svntest
.Failure(
1045 "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.")
1049 "handle tags rooted in a redeleted revision"
1050 conv
= ensure_conversion('overdead')
1053 def no_trunk_prune(**kw
):
1054 "ensure that trunk doesn't get pruned"
1055 conv
= ensure_conversion('overdead', **kw
)
1056 for rev
in conv
.logs
.keys():
1057 rev_logs
= conv
.logs
[rev
]
1058 if rev_logs
.get_path_op('/%(trunk)s') == 'D':
1059 raise svntest
.Failure
1062 def no_trunk_prune_variants():
1063 "no trunk pruning, with alternate repo layout"
1064 no_trunk_prune(trunk
='a', branches
='b', tags
='c')
1065 no_trunk_prune(trunk
='a/1', branches
='b/1', tags
='c/1')
1066 no_trunk_prune(trunk
='a/1', branches
='a/2', tags
='a/3')
1069 def double_delete():
1070 "file deleted twice, in the root of the repository"
1071 # This really tests several things: how we handle a file that's
1072 # removed (state 'dead') in two successive revisions; how we
1073 # handle a file in the root of the repository (there were some
1074 # bugs in cvs2svn's svn path construction for top-level files); and
1075 # the --no-prune option.
1076 conv
= ensure_conversion(
1077 'double-delete', args
=['--trunk-only', '--no-prune'])
1079 path
= '/%(trunk)s/twice-removed'
1081 conv
.logs
[rev
].check_change(path
, 'A')
1082 conv
.logs
[rev
].check_msg('Initial revision')
1084 conv
.logs
[rev
+ 1].check_change(path
, 'D')
1085 conv
.logs
[rev
+ 1].check_msg('Remove this file for the first time.')
1087 if conv
.logs
[rev
+ 1].get_path_op('/%(trunk)s') is not None:
1088 raise svntest
.Failure
1092 "branch created from both trunk and another branch"
1093 # See test-data/split-branch-cvsrepos/README.
1095 # The conversion will fail if the bug is present, and
1096 # ensure_conversion will raise svntest.Failure.
1097 conv
= ensure_conversion('split-branch')
1100 def resync_misgroups():
1101 "resyncing should not misorder commit groups"
1102 # See test-data/resync-misgroups-cvsrepos/README.
1104 # The conversion will fail if the bug is present, and
1105 # ensure_conversion will raise svntest.Failure.
1106 conv
= ensure_conversion('resync-misgroups')
1109 def tagged_branch_and_trunk(**kw
):
1110 "allow tags with mixed trunk and branch sources"
1111 conv
= ensure_conversion('tagged-branch-n-trunk', **kw
)
1113 tags
= kw
.get('tags', 'tags')
1115 a_path
= os
.path
.join(conv
.get_wc(), tags
, 'some-tag', 'a.txt')
1116 b_path
= os
.path
.join(conv
.get_wc(), tags
, 'some-tag', 'b.txt')
1117 if not (os
.path
.exists(a_path
) and os
.path
.exists(b_path
)):
1118 raise svntest
.Failure
1119 if (open(a_path
, 'r').read().find('1.24') == -1) \
1120 or (open(b_path
, 'r').read().find('1.5') == -1):
1121 raise svntest
.Failure
1124 def tagged_branch_and_trunk_variants():
1125 "mixed tags, with alternate repo layout"
1126 tagged_branch_and_trunk(trunk
='a/1', branches
='a/2', tags
='a/3')
1130 "never use the rev-in-progress as a copy source"
1131 # See issue #1427 and r8544.
1132 conv
= ensure_conversion('enroot-race')
1134 conv
.logs
[rev
].check_changes((
1135 ('/%(branches)s/mybranch (from /%(trunk)s:7)', 'A'),
1136 ('/%(branches)s/mybranch/proj/a.txt', 'D'),
1137 ('/%(branches)s/mybranch/proj/b.txt', 'D'),
1139 conv
.logs
[rev
+ 1].check_changes((
1140 ('/%(branches)s/mybranch/proj/c.txt', 'M'),
1141 ('/%(trunk)s/proj/a.txt', 'M'),
1142 ('/%(trunk)s/proj/b.txt', 'M'),
1146 def enroot_race_obo():
1147 "do use the last completed rev as a copy source"
1148 conv
= ensure_conversion('enroot-race-obo')
1149 conv
.logs
[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A')
1150 if not len(conv
.logs
) == 3:
1151 raise svntest
.Failure
1154 def branch_delete_first(**kw
):
1155 "correctly handle deletion as initial branch action"
1156 # See test-data/branch-delete-first-cvsrepos/README.
1158 # The conversion will fail if the bug is present, and
1159 # ensure_conversion would raise svntest.Failure.
1160 conv
= ensure_conversion('branch-delete-first', **kw
)
1162 branches
= kw
.get('branches', 'branches')
1164 # 'file' was deleted from branch-1 and branch-2, but not branch-3
1166 os
.path
.join(conv
.get_wc(), branches
, 'branch-1', 'file')):
1167 raise svntest
.Failure
1169 os
.path
.join(conv
.get_wc(), branches
, 'branch-2', 'file')):
1170 raise svntest
.Failure
1171 if not os
.path
.exists(
1172 os
.path
.join(conv
.get_wc(), branches
, 'branch-3', 'file')):
1173 raise svntest
.Failure
1176 def branch_delete_first_variants():
1177 "initial delete, with alternate repo layout"
1178 branch_delete_first(trunk
='a/1', branches
='a/2', tags
='a/3')
1181 def nonascii_filenames():
1182 "non ascii files converted incorrectly"
1185 # on a en_US.iso-8859-1 machine this test fails with
1186 # svn: Can't recode ...
1188 # as described in the issue
1190 # on a en_US.UTF-8 machine this test fails with
1191 # svn: Malformed XML ...
1193 # which means at least it fails. Unfortunately it won't fail
1194 # with the same error...
1196 # mangle current locale settings so we know we're not running
1197 # a UTF-8 locale (which does not exhibit this problem)
1198 current_locale
= locale
.getlocale()
1199 new_locale
= 'en_US.ISO8859-1'
1200 locale_changed
= None
1202 # From http://docs.python.org/lib/module-sys.html
1204 # getfilesystemencoding():
1206 # Return the name of the encoding used to convert Unicode filenames
1207 # into system file names, or None if the system default encoding is
1208 # used. The result value depends on the operating system:
1210 # - On Windows 9x, the encoding is ``mbcs''.
1211 # - On Mac OS X, the encoding is ``utf-8''.
1212 # - On Unix, the encoding is the user's preference according to the
1213 # result of nl_langinfo(CODESET), or None if the
1214 # nl_langinfo(CODESET) failed.
1215 # - On Windows NT+, file names are Unicode natively, so no conversion is
1218 # So we're going to skip this test on Mac OS X for now.
1219 if sys
.platform
== "darwin":
1223 # change locale to non-UTF-8 locale to generate latin1 names
1224 locale
.setlocale(locale
.LC_ALL
, # this might be too broad?
1227 except locale
.Error
:
1231 srcrepos_path
= os
.path
.join(test_data_dir
,'main-cvsrepos')
1232 dstrepos_path
= os
.path
.join(test_data_dir
,'non-ascii-cvsrepos')
1233 if not os
.path
.exists(dstrepos_path
):
1234 # create repos from existing main repos
1235 shutil
.copytree(srcrepos_path
, dstrepos_path
)
1236 base_path
= os
.path
.join(dstrepos_path
, 'single-files')
1237 shutil
.copyfile(os
.path
.join(base_path
, 'twoquick,v'),
1238 os
.path
.join(base_path
, 'two\366uick,v'))
1239 new_path
= os
.path
.join(dstrepos_path
, 'single\366files')
1240 os
.rename(base_path
, new_path
)
1242 # if ensure_conversion can generate a
1243 conv
= ensure_conversion('non-ascii', args
=['--encoding=latin1'])
1246 locale
.setlocale(locale
.LC_ALL
, current_locale
)
1247 svntest
.main
.safe_rmtree(dstrepos_path
)
1250 def vendor_branch_sameness():
1251 "avoid spurious changes for initial revs"
1252 conv
= ensure_conversion('vendor-branch-sameness')
1254 # There are four files in the repository:
1256 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1257 # the same contents, the file's default branch is 1.1.1,
1258 # and both revisions are in state 'Exp'.
1260 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1261 # 1.1 (the addition of a line of text).
1263 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1265 # d.txt: This file was created by 'cvs add' instead of import, so
1266 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1267 # The timestamp on the add is exactly the same as for the
1268 # imports of the other files.
1270 # (Log messages for the same revisions are the same in all files.)
1272 # What we expect to see is everyone added in r1, then trunk/proj
1273 # copied in r2. In the copy, only a.txt should be left untouched;
1274 # b.txt should be 'M'odified, and (for different reasons) c.txt and
1275 # d.txt should be 'D'eleted.
1278 conv
.logs
[rev
].check('Initial revision', (
1279 ('/%(trunk)s/proj', 'A'),
1280 ('/%(trunk)s/proj/a.txt', 'A'),
1281 ('/%(trunk)s/proj/b.txt', 'A'),
1282 ('/%(trunk)s/proj/c.txt', 'A'),
1283 ('/%(trunk)s/proj/d.txt', 'A'),
1286 conv
.logs
[rev
+ 1].check(sym_log_msg('vbranchA'), (
1287 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1288 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1291 conv
.logs
[rev
+ 2].check('First vendor branch revision.', (
1292 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1293 ('/%(branches)s/vbranchA/proj/c.txt', 'D'),
1297 def default_branches():
1298 "handle default branches correctly"
1299 conv
= ensure_conversion('default-branches')
1301 # There are seven files in the repository:
1304 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1305 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1306 # committed (thus losing the default branch "1.1.1"), then
1307 # 1.1.1.4 was imported. All vendor import release tags are
1311 # Like a.txt, but without rev 1.2.
1314 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1317 # Same as the previous two, but 1.1.1 branch is unlabeled.
1320 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1322 # deleted-on-vendor-branch.txt,v:
1323 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1325 # added-then-imported.txt,v:
1326 # Added with 'cvs add' to create 1.1, then imported with
1327 # completely different contents to create 1.1.1.1, therefore
1328 # never had a default branch.
1331 conv
.logs
[18].check(sym_log_msg('vtag-4',1), (
1332 ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:16)', 'A'),
1333 ('/%(tags)s/vtag-4/proj/d.txt '
1334 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:16)', 'A'),
1337 conv
.logs
[6].check(sym_log_msg('vtag-1',1), (
1338 ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:5)', 'A'),
1339 ('/%(tags)s/vtag-1/proj/d.txt '
1340 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'),
1343 conv
.logs
[9].check(sym_log_msg('vtag-2',1), (
1344 ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:7)', 'A'),
1345 ('/%(tags)s/vtag-2/proj/d.txt '
1346 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:7)', 'A'),
1349 conv
.logs
[12].check(sym_log_msg('vtag-3',1), (
1350 ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:10)', 'A'),
1351 ('/%(tags)s/vtag-3/proj/d.txt '
1352 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:10)', 'A'),
1353 ('/%(tags)s/vtag-3/proj/e.txt '
1354 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:10)', 'A'),
1357 conv
.logs
[17].check("This commit was generated by cvs2svn "
1358 "to compensate for changes in r16,", (
1359 ('/%(trunk)s/proj/b.txt '
1360 '(from /%(branches)s/vbranchA/proj/b.txt:16)', 'R'),
1361 ('/%(trunk)s/proj/c.txt '
1362 '(from /%(branches)s/vbranchA/proj/c.txt:16)', 'R'),
1363 ('/%(trunk)s/proj/d.txt '
1364 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:16)', 'R'),
1365 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1366 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:16)',
1368 ('/%(trunk)s/proj/e.txt '
1369 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:16)', 'R'),
1372 conv
.logs
[16].check("Import (vbranchA, vtag-4).", (
1373 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1374 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1375 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1376 ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!!
1377 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1378 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1379 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'),
1382 conv
.logs
[15].check(sym_log_msg('vbranchA'), (
1383 ('/%(branches)s/vbranchA/proj/added-then-imported.txt '
1384 '(from /%(trunk)s/proj/added-then-imported.txt:14)', 'A'),
1387 conv
.logs
[14].check("Add a file to the working copy.", (
1388 ('/%(trunk)s/proj/added-then-imported.txt', 'A'),
1391 conv
.logs
[13].check("First regular commit, to a.txt, on vtag-3.", (
1392 ('/%(trunk)s/proj/a.txt', 'M'),
1395 conv
.logs
[11].check("This commit was generated by cvs2svn "
1396 "to compensate for changes in r10,", (
1397 ('/%(trunk)s/proj/a.txt '
1398 '(from /%(branches)s/vbranchA/proj/a.txt:10)', 'R'),
1399 ('/%(trunk)s/proj/b.txt '
1400 '(from /%(branches)s/vbranchA/proj/b.txt:10)', 'R'),
1401 ('/%(trunk)s/proj/c.txt '
1402 '(from /%(branches)s/vbranchA/proj/c.txt:10)', 'R'),
1403 ('/%(trunk)s/proj/d.txt '
1404 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:10)', 'R'),
1405 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'),
1406 ('/%(trunk)s/proj/e.txt '
1407 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:10)', 'R'),
1410 conv
.logs
[10].check("Import (vbranchA, vtag-3).", (
1411 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1412 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1413 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1414 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1415 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1416 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'),
1419 conv
.logs
[8].check("This commit was generated by cvs2svn "
1420 "to compensate for changes in r7,", (
1421 ('/%(trunk)s/proj/a.txt '
1422 '(from /%(branches)s/vbranchA/proj/a.txt:7)', 'R'),
1423 ('/%(trunk)s/proj/b.txt '
1424 '(from /%(branches)s/vbranchA/proj/b.txt:7)', 'R'),
1425 ('/%(trunk)s/proj/c.txt '
1426 '(from /%(branches)s/vbranchA/proj/c.txt:7)', 'R'),
1427 ('/%(trunk)s/proj/d.txt '
1428 '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:7)', 'R'),
1429 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt '
1430 '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:7)',
1432 ('/%(trunk)s/proj/e.txt '
1433 '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:7)', 'R'),
1436 conv
.logs
[7].check("Import (vbranchA, vtag-2).", (
1437 ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'),
1438 ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'),
1439 ('/%(branches)s/vbranchA/proj/a.txt', 'M'),
1440 ('/%(branches)s/vbranchA/proj/b.txt', 'M'),
1441 ('/%(branches)s/vbranchA/proj/c.txt', 'M'),
1442 ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'),
1445 conv
.logs
[5].check("Import (vbranchA, vtag-1).", ())
1447 conv
.logs
[4].check(sym_log_msg('vbranchA'), (
1448 ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'),
1449 ('/%(branches)s/vbranchA/proj/d.txt', 'D'),
1450 ('/%(branches)s/vbranchA/proj/e.txt', 'D'),
1453 conv
.logs
[3].check(sym_log_msg('unlabeled-1.1.1'), (
1454 ('/%(branches)s/unlabeled-1.1.1 (from /%(trunk)s:2)', 'A'),
1455 ('/%(branches)s/unlabeled-1.1.1/proj/a.txt', 'D'),
1456 ('/%(branches)s/unlabeled-1.1.1/proj/b.txt', 'D'),
1457 ('/%(branches)s/unlabeled-1.1.1/proj/c.txt', 'D'),
1458 ('/%(branches)s/unlabeled-1.1.1/proj/deleted-on-vendor-branch.txt', 'D'),
1461 conv
.logs
[2].check("Initial revision", (
1462 ('/%(trunk)s/proj', 'A'),
1463 ('/%(trunk)s/proj/a.txt', 'A'),
1464 ('/%(trunk)s/proj/b.txt', 'A'),
1465 ('/%(trunk)s/proj/c.txt', 'A'),
1466 ('/%(trunk)s/proj/d.txt', 'A'),
1467 ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'),
1468 ('/%(trunk)s/proj/e.txt', 'A'),
1472 def compose_tag_three_sources():
1473 "compose a tag from three sources"
1474 conv
= ensure_conversion('compose-tag-three-sources')
1476 conv
.logs
[2].check("Add on trunk", (
1477 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'),
1478 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'),
1479 ('/%(trunk)s/tagged-on-trunk-1.1', 'A'),
1480 ('/%(trunk)s/tagged-on-b1', 'A'),
1481 ('/%(trunk)s/tagged-on-b2', 'A'),
1484 conv
.logs
[3].check(sym_log_msg('b1'), (
1485 ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'),
1488 conv
.logs
[4].check(sym_log_msg('b2'), (
1489 ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'),
1492 conv
.logs
[5].check("Commit on branch b1", (
1493 ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'),
1494 ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'),
1495 ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'),
1496 ('/%(branches)s/b1/tagged-on-b1', 'M'),
1497 ('/%(branches)s/b1/tagged-on-b2', 'M'),
1500 conv
.logs
[6].check("Commit on branch b2", (
1501 ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'),
1502 ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'),
1503 ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'),
1504 ('/%(branches)s/b2/tagged-on-b1', 'M'),
1505 ('/%(branches)s/b2/tagged-on-b2', 'M'),
1508 conv
.logs
[7].check("Commit again on trunk", (
1509 ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'),
1510 ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'),
1511 ('/%(trunk)s/tagged-on-trunk-1.1', 'M'),
1512 ('/%(trunk)s/tagged-on-b1', 'M'),
1513 ('/%(trunk)s/tagged-on-b2', 'M'),
1516 conv
.logs
[8].check(sym_log_msg('T',1), (
1517 ('/%(tags)s/T (from /%(trunk)s:7)', 'A'),
1518 ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:7)', 'R'),
1519 ('/%(tags)s/T/tagged-on-trunk-1.1 '
1520 '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'),
1521 ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:7)', 'R'),
1525 def pass5_when_to_fill():
1526 "reserve a svn revnum for a fill only when required"
1527 # The conversion will fail if the bug is present, and
1528 # ensure_conversion would raise svntest.Failure.
1529 conv
= ensure_conversion('pass5-when-to-fill')
1532 def empty_trunk(**kw
):
1533 "don't break when the trunk is empty"
1534 # The conversion will fail if the bug is present, and
1535 # ensure_conversion would raise svntest.Failure.
1536 conv
= ensure_conversion('empty-trunk', **kw
)
1539 def empty_trunk_variants():
1540 "empty trunk, with alternate repo layout"
1541 empty_trunk(trunk
='a', branches
='b', tags
='c')
1542 empty_trunk(trunk
='a/1', branches
='a/2', tags
='a/3')
1545 def no_spurious_svn_commits():
1546 "ensure that we don't create any spurious commits"
1547 conv
= ensure_conversion('phoenix')
1549 # Check spurious commit that could be created in CVSCommit._pre_commit
1550 # (When you add a file on a branch, CVS creates a trunk revision
1551 # in state 'dead'. If the log message of that commit is equal to
1552 # the one that CVS generates, we do not ever create a 'fill'
1553 # SVNCommit for it.)
1555 # and spurious commit that could be created in CVSCommit._commit
1556 # (When you add a file on a branch, CVS creates a trunk revision
1557 # in state 'dead'. If the log message of that commit is equal to
1558 # the one that CVS generates, we do not create a primary SVNCommit
1560 conv
.logs
[18].check('File added on branch xiphophorus', (
1561 ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'),
1564 # Check to make sure that a commit *is* generated:
1565 # (When you add a file on a branch, CVS creates a trunk revision
1566 # in state 'dead'. If the log message of that commit is NOT equal
1567 # to the one that CVS generates, we create a primary SVNCommit to
1568 # serve as a home for the log message in question.
1569 conv
.logs
[19].check('file added-on-branch2.txt was initially added on '
1570 + 'branch xiphophorus,\nand this log message was tweaked', ())
1572 # Check spurious commit that could be created in
1573 # CVSRevisionAggregator.attempt_to_commit_symbols
1574 # (We shouldn't consider a CVSRevision whose op is OP_DEAD as a
1575 # candidate for the LastSymbolicNameDatabase.
1576 conv
.logs
[20].check('This file was also added on branch xiphophorus,', (
1577 ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'),
1581 def peer_path_pruning(**kw
):
1582 "make sure that filling prunes paths correctly"
1583 conv
= ensure_conversion('peer-path-pruning', **kw
)
1584 conv
.logs
[8].check(sym_log_msg('BRANCH'), (
1585 ('/%(branches)s/BRANCH (from /%(trunk)s:6)', 'A'),
1586 ('/%(branches)s/BRANCH/bar', 'D'),
1587 ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:7)', 'R'),
1591 def peer_path_pruning_variants():
1592 "filling prune paths, with alternate repo layout"
1593 peer_path_pruning(trunk
='a/1', branches
='a/2', tags
='a/3')
1596 def invalid_closings_on_trunk():
1597 "verify correct revs are copied to default branches"
1598 # The conversion will fail if the bug is present, and
1599 # ensure_conversion would raise svntest.Failure.
1600 conv
= ensure_conversion('invalid-closings-on-trunk')
1603 def individual_passes():
1604 "run each pass individually"
1605 conv
= ensure_conversion('main')
1606 conv2
= ensure_conversion('main', passbypass
=1)
1608 if conv
.logs
!= conv2
.logs
:
1609 raise svntest
.Failure
1613 "reveal a big bug in our resync algorithm"
1614 # This will fail if the bug is present
1615 conv
= ensure_conversion('resync-bug')
1618 def branch_from_default_branch():
1619 "reveal a bug in our default branch detection code"
1620 conv
= ensure_conversion('branch-from-default-branch')
1622 # This revision will be a default branch synchronization only
1623 # if cvs2svn is correctly determining default branch revisions.
1625 # The bug was that cvs2svn was treating revisions on branches off of
1626 # default branches as default branch revisions, resulting in
1627 # incorrectly regarding the branch off of the default branch as a
1628 # non-trunk default branch. Crystal clear? I thought so. See
1629 # issue #42 for more incoherent blathering.
1630 conv
.logs
[6].check("This commit was generated by cvs2svn", (
1631 ('/%(trunk)s/proj/file.txt '
1632 '(from /%(branches)s/upstream/proj/file.txt:5)', 'R'),
1635 def file_in_attic_too():
1636 "die if a file exists in and out of the attic"
1638 ensure_conversion('file-in-attic-too')
1639 raise MissingErrorException
1640 except svntest
.Failure
:
1643 def symbolic_name_filling_guide():
1644 "reveal a big bug in our SymbolicNameFillingGuide"
1645 # This will fail if the bug is present
1646 conv
= ensure_conversion('symbolic-name-overfill')
1649 # Helpers for tests involving file contents and properties.
1651 class NodeTreeWalkException
:
1652 "Exception class for node tree traversals."
1655 def node_for_path(node
, path
):
1656 "In the tree rooted under SVNTree NODE, return the node at PATH."
1657 if node
.name
!= '__SVN_ROOT_NODE':
1658 raise NodeTreeWalkException
1659 path
= path
.strip('/')
1660 components
= path
.split('/')
1661 for component
in components
:
1662 node
= svntest
.tree
.get_child(node
, component
)
1665 # Helper for tests involving properties.
1666 def props_for_path(node
, path
):
1667 "In the tree rooted under SVNTree NODE, return the prop dict for PATH."
1668 return node_for_path(node
, path
).props
1672 "test eol settings and mime types together"
1673 ###TODO: It's a bit klugey to construct this path here. But so far
1674 ### there's only one test with a mime.types file. If we have more,
1675 ### we should abstract this into some helper, which would be located
1676 ### near ensure_conversion(). Note that it is a convention of this
1677 ### test suite for a mime.types file to be located in the top level
1678 ### of the CVS repository to which it applies.
1679 mime_path
= os
.path
.join('..', test_data_dir
, 'eol-mime-cvsrepos',
1682 # We do four conversions. Each time, we pass --mime-types=FILE with
1683 # the same FILE, but vary --no-default-eol and --eol-from-mime-type.
1684 # Thus there's one conversion with neither flag, one with just the
1685 # former, one with just the latter, and one with both.
1687 # In two of the four conversions, we pass --cvs-revnums to make
1688 # certain that there are no bad interactions.
1690 # The files are as follows:
1692 # trunk/foo.txt: no -kb, mime file says nothing.
1693 # trunk/foo.xml: no -kb, mime file says text.
1694 # trunk/foo.zip: no -kb, mime file says non-text.
1695 # trunk/foo.bin: has -kb, mime file says nothing.
1696 # trunk/foo.csv: has -kb, mime file says text.
1697 # trunk/foo.dbf: has -kb, mime file says non-text.
1699 ## Neither --no-default-eol nor --eol-from-mime-type. ##
1700 conv
= ensure_conversion(
1701 'eol-mime', args
=['--mime-types=%s' % mime_path
, '--cvs-revnums'])
1703 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1705 ('trunk/foo.txt', ['native', None, '1.2']),
1706 ('trunk/foo.xml', ['native', 'text/xml', '1.2']),
1707 ('trunk/foo.zip', ['native', 'application/zip', '1.2']),
1708 ('trunk/foo.bin', [None, 'application/octet-stream', '1.2']),
1709 ('trunk/foo.csv', [None, 'text/csv', '1.2']),
1710 ('trunk/foo.dbf', [None, 'application/what-is-dbf', '1.2']),
1714 ## Just --no-default-eol, not --eol-from-mime-type. ##
1715 conv
= ensure_conversion(
1716 'eol-mime', args
=['--mime-types=%s' % mime_path
, '--no-default-eol'])
1718 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1720 ('trunk/foo.txt', [None, None, None]),
1721 ('trunk/foo.xml', [None, 'text/xml', None]),
1722 ('trunk/foo.zip', [None, 'application/zip', None]),
1723 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
1724 ('trunk/foo.csv', [None, 'text/csv', None]),
1725 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
1729 ## Just --eol-from-mime-type, not --no-default-eol. ##
1730 conv
= ensure_conversion('eol-mime', args
=[
1731 '--mime-types=%s' % mime_path
, '--eol-from-mime-type', '--cvs-revnums'
1734 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1736 ('trunk/foo.txt', ['native', None, '1.2']),
1737 ('trunk/foo.xml', ['native', 'text/xml', '1.2']),
1738 ('trunk/foo.zip', [None, 'application/zip', '1.2']),
1739 ('trunk/foo.bin', [None, 'application/octet-stream', '1.2']),
1740 ('trunk/foo.csv', [None, 'text/csv', '1.2']),
1741 ('trunk/foo.dbf', [None, 'application/what-is-dbf', '1.2']),
1745 ## Both --no-default-eol and --eol-from-mime-type. ##
1746 conv
= ensure_conversion('eol-mime', args
=[
1747 '--mime-types=%s' % mime_path
, '--eol-from-mime-type',
1748 '--no-default-eol'])
1750 ['svn:eol-style', 'svn:mime-type', 'cvs2svn:cvs-rev'],
1752 ('trunk/foo.txt', [None, None, None]),
1753 ('trunk/foo.xml', ['native', 'text/xml', None]),
1754 ('trunk/foo.zip', [None, 'application/zip', None]),
1755 ('trunk/foo.bin', [None, 'application/octet-stream', None]),
1756 ('trunk/foo.csv', [None, 'text/csv', None]),
1757 ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]),
1763 "test setting of svn:keywords property among others"
1764 conv
= ensure_conversion('keywords')
1766 ['svn:keywords', 'svn:eol-style', 'svn:mime-type'],
1768 ('trunk/foo.default', ['Author Date Id Revision', 'native', None]),
1769 ('trunk/foo.kkvl', ['Author Date Id Revision', 'native', None]),
1770 ('trunk/foo.kkv', ['Author Date Id Revision', 'native', None]),
1771 ('trunk/foo.kb', [None, None, 'application/octet-stream']),
1772 ('trunk/foo.kk', [None, 'native', None]),
1773 ('trunk/foo.ko', [None, 'native', None]),
1774 ('trunk/foo.kv', [None, 'native', None]),
1780 "test setting of svn:ignore property"
1781 conv
= ensure_conversion('cvsignore')
1782 wc_tree
= conv
.get_wc_tree()
1783 topdir_props
= props_for_path(wc_tree
, 'trunk/proj')
1784 subdir_props
= props_for_path(wc_tree
, '/trunk/proj/subdir')
1786 if topdir_props
['svn:ignore'] != \
1787 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1788 raise svntest
.Failure
1790 if subdir_props
['svn:ignore'] != \
1791 '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n':
1792 raise svntest
.Failure
1796 "test that CVS can still do what RCS can't"
1797 # See issues 4, 11, 29 for the bugs whose regression we're testing for.
1798 conv
= ensure_conversion('requires-cvs', args
=["--use-cvs"])
1800 atsign_contents
= file(
1801 os
.path
.join(conv
.get_wc(), "trunk", "atsign-add")).read()
1803 os
.path
.join(conv
.get_wc(), "trunk", "client_lock.idl")).read()
1805 if atsign_contents
[-1:] == "@":
1806 raise svntest
.Failure
1807 if cl_contents
.find("gregh\n//\n//Integration for locks") < 0:
1808 raise svntest
.Failure
1810 if not (conv
.logs
[21].author
== "William Lyon Phelps III" and
1811 conv
.logs
[20].author
== "j random"):
1812 raise svntest
.Failure
1815 def questionable_branch_names():
1816 "test that we can handle weird branch names"
1817 conv
= ensure_conversion('questionable-symbols')
1818 # If the conversion succeeds, then we're okay. We could check the
1819 # actual branch paths, too, but the main thing is to know that the
1820 # conversion doesn't fail.
1823 def questionable_tag_names():
1824 "test that we can handle weird tag names"
1825 conv
= ensure_conversion('questionable-symbols')
1826 for tag_name
in ['Tag_A', 'TagWith--Backslash_E', 'TagWith++Slash_Z']:
1827 conv
.find_tag_log(tag_name
).check(sym_log_msg(tag_name
,1), (
1828 ('/%(tags)s/' + tag_name
+ ' (from /trunk:8)', 'A'),
1832 def revision_reorder_bug():
1833 "reveal a bug that reorders file revisions"
1834 conv
= ensure_conversion('revision-reorder-bug')
1835 # If the conversion succeeds, then we're okay. We could check the
1836 # actual revisions, too, but the main thing is to know that the
1837 # conversion doesn't fail.
1841 "test that exclude really excludes everything"
1842 conv
= ensure_conversion('main', args
=['--exclude=.*'])
1843 for log
in conv
.logs
.values():
1844 for item
in log
.changed_paths
.keys():
1845 if item
.startswith('/branches/') or item
.startswith('/tags/'):
1846 raise svntest
.Failure
1849 def vendor_branch_delete_add():
1850 "add trunk file that was deleted on vendor branch"
1851 # This will error if the bug is present
1852 conv
= ensure_conversion('vendor-branch-delete-add')
1855 def resync_pass2_pull_forward():
1856 "ensure pass2 doesn't pull rev too far forward"
1857 conv
= ensure_conversion('resync-pass2-pull-forward')
1858 # If the conversion succeeds, then we're okay. We could check the
1859 # actual revisions, too, but the main thing is to know that the
1860 # conversion doesn't fail.
1864 "only LFs for svn:eol-style=native files"
1865 conv
= ensure_conversion('native-eol')
1866 lines
= run_program(svntest
.main
.svnadmin_binary
, None, 'dump', '-q',
1868 # Verify that all files in the dump have LF EOLs. We're actually
1869 # testing the whole dump file, but the dump file itself only uses
1870 # LF EOLs, so we're safe.
1872 if line
[-1] != '\n' or line
[:-1].find('\r') != -1:
1873 raise svntest
.Failure
1877 "reveal a bug that created a branch twice"
1878 conv
= ensure_conversion('double-fill')
1879 # If the conversion succeeds, then we're okay. We could check the
1880 # actual revisions, too, but the main thing is to know that the
1881 # conversion doesn't fail.
1884 def resync_pass2_push_backward():
1885 "ensure pass2 doesn't push rev too far backward"
1886 conv
= ensure_conversion('resync-pass2-push-backward')
1887 # If the conversion succeeds, then we're okay. We could check the
1888 # actual revisions, too, but the main thing is to know that the
1889 # conversion doesn't fail.
1893 "reveal a bug that added a branch file twice"
1894 conv
= ensure_conversion('double-add')
1895 # If the conversion succeeds, then we're okay. We could check the
1896 # actual revisions, too, but the main thing is to know that the
1897 # conversion doesn't fail.
1900 def bogus_branch_copy():
1901 "reveal a bug that copies a branch file wrongly"
1902 conv
= ensure_conversion('bogus-branch-copy')
1903 # If the conversion succeeds, then we're okay. We could check the
1904 # actual revisions, too, but the main thing is to know that the
1905 # conversion doesn't fail.
1908 def nested_ttb_directories():
1909 "require error if ttb directories are not disjoint"
1911 {'trunk' : 'a', 'branches' : 'a',},
1912 {'trunk' : 'a', 'tags' : 'a',},
1913 {'branches' : 'a', 'tags' : 'a',},
1914 # This option conflicts with the default trunk path:
1915 {'branches' : 'trunk',},
1916 # Try some nested directories:
1917 {'trunk' : 'a', 'branches' : 'a/b',},
1918 {'trunk' : 'a/b', 'tags' : 'a/b/c/d',},
1919 {'branches' : 'a', 'tags' : 'a/b',},
1922 for opts
in opts_list
:
1925 'main', error_re
=r
'.*paths .* and .* are not disjoint\.', **opts
1927 raise MissingErrorException
1928 except svntest
.Failure
:
1932 def auto_props_ignore_case():
1933 "test auto-props (case-insensitive)"
1934 ### TODO: It's a bit klugey to construct this path here. See also
1935 ### the comment in eol_mime().
1936 auto_props_path
= os
.path
.join('..', test_data_dir
, 'eol-mime-cvsrepos',
1939 # The files are as follows:
1941 # trunk/foo.txt: no -kb, mime auto-prop says nothing.
1942 # trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF.
1943 # trunk/foo.zip: no -kb, mime auto-prop says non-text.
1944 # trunk/foo.bin: has -kb, mime auto-prop says nothing.
1945 # trunk/foo.csv: has -kb, mime auto-prop says text.
1946 # trunk/foo.dbf: has -kb, mime auto-prop says non-text.
1947 # trunk/foo.UPCASE1: no -kb, no mime type.
1948 # trunk/foo.UPCASE2: no -kb, no mime type.
1950 conv
= ensure_conversion(
1952 args
=['--auto-props=%s' % auto_props_path
, '--auto-props-ignore-case'])
1954 ['myprop', 'svn:eol-style', 'svn:mime-type'],
1956 ('trunk/foo.txt', ['txt', 'native', None]),
1957 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml']),
1958 ('trunk/foo.zip', ['zip', 'native', 'application/zip']),
1959 ('trunk/foo.bin', ['bin', None, 'application/octet-stream']),
1960 ('trunk/foo.csv', ['csv', None, 'text/csv']),
1961 ('trunk/foo.dbf', ['dbf', None, 'application/what-is-dbf']),
1962 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None]),
1963 ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None]),
1969 "test auto-props (case-sensitive)"
1970 # See auto_props for comments.
1971 auto_props_path
= os
.path
.join('..', test_data_dir
, 'eol-mime-cvsrepos',
1974 conv
= ensure_conversion(
1975 'eol-mime', args
=['--auto-props=%s' % auto_props_path
])
1977 ['myprop', 'svn:eol-style', 'svn:mime-type'],
1979 ('trunk/foo.txt', ['txt', 'native', None]),
1980 ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml']),
1981 ('trunk/foo.zip', ['zip', 'native', 'application/zip']),
1982 ('trunk/foo.bin', ['bin', None, 'application/octet-stream']),
1983 ('trunk/foo.csv', ['csv', None, 'text/csv']),
1984 ('trunk/foo.dbf', ['dbf', None, 'application/what-is-dbf']),
1985 ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None]),
1986 ('trunk/foo.UPCASE2', [None, 'native', None]),
1991 def ctrl_char_in_filename():
1992 "do not allow control characters in filenames"
1995 srcrepos_path
= os
.path
.join(test_data_dir
,'main-cvsrepos')
1996 dstrepos_path
= os
.path
.join(test_data_dir
,'ctrl-char-filename-cvsrepos')
1997 if os
.path
.exists(dstrepos_path
):
1998 svntest
.main
.safe_rmtree(dstrepos_path
)
2000 # create repos from existing main repos
2001 shutil
.copytree(srcrepos_path
, dstrepos_path
)
2002 base_path
= os
.path
.join(dstrepos_path
, 'single-files')
2004 shutil
.copyfile(os
.path
.join(base_path
, 'twoquick,v'),
2005 os
.path
.join(base_path
, 'two\rquick,v'))
2007 # Operating systems that don't allow control characters in
2008 # filenames will hopefully have thrown an exception; in that
2009 # case, just skip this test.
2013 conv
= ensure_conversion(
2014 'ctrl-char-filename',
2015 error_re
=(r
'.*Character .* in filename .* '
2016 r
'is not supported by subversion\.'),
2018 raise MissingErrorException
2019 except svntest
.Failure
:
2022 svntest
.main
.safe_rmtree(dstrepos_path
)
2025 def commit_dependencies():
2026 "interleaved and multi-branch commits to same files"
2027 conv
= ensure_conversion("commit-dependencies")
2028 conv
.logs
[2].check('adding', (
2029 ('/%(trunk)s/interleaved', 'A'),
2030 ('/%(trunk)s/interleaved/file1', 'A'),
2031 ('/%(trunk)s/interleaved/file2', 'A'),
2033 conv
.logs
[3].check('big commit', (
2034 ('/%(trunk)s/interleaved/file1', 'M'),
2035 ('/%(trunk)s/interleaved/file2', 'M'),
2037 conv
.logs
[4].check('dependant small commit', (
2038 ('/%(trunk)s/interleaved/file1', 'M'),
2040 conv
.logs
[5].check('adding', (
2041 ('/%(trunk)s/multi-branch', 'A'),
2042 ('/%(trunk)s/multi-branch/file1', 'A'),
2043 ('/%(trunk)s/multi-branch/file2', 'A'),
2045 conv
.logs
[6].check(sym_log_msg("branch"), (
2046 ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'),
2047 ('/%(branches)s/branch/interleaved', 'D'),
2049 conv
.logs
[7].check('multi-branch-commit', (
2050 ('/%(trunk)s/multi-branch/file1', 'M'),
2051 ('/%(trunk)s/multi-branch/file2', 'M'),
2052 ('/%(branches)s/branch/multi-branch/file1', 'M'),
2053 ('/%(branches)s/branch/multi-branch/file2', 'M'),
2057 #----------------------------------------------------------------------
2059 ########################################################################
2062 # list all tests here, starting with None:
2069 interleaved_commits
,
2072 simple_branch_commits
,
2073 mixed_time_tag
, # 10
2074 mixed_time_branch_with_added_file
,
2086 tagged_branch_and_trunk
,
2089 branch_delete_first
,
2091 vendor_branch_sameness
,
2093 compose_tag_three_sources
, # 30
2097 no_spurious_svn_commits
,
2098 invalid_closings_on_trunk
,
2101 branch_from_default_branch
,
2103 symbolic_name_filling_guide
, # 40
2108 questionable_branch_names
,
2109 questionable_tag_names
,
2110 revision_reorder_bug
,
2112 vendor_branch_delete_add
,
2113 resync_pass2_pull_forward
, # 50
2116 resync_pass2_push_backward
,
2119 nested_ttb_directories
,
2120 prune_with_care_variants
,
2121 simple_tags_variants
,
2122 phoenix_branch_variants
,
2123 no_trunk_prune_variants
, # 60
2124 tagged_branch_and_trunk_variants
,
2125 branch_delete_first_variants
,
2126 empty_trunk_variants
,
2127 peer_path_pruning_variants
,
2128 auto_props_ignore_case
,
2130 ctrl_char_in_filename
,
2131 commit_dependencies
,
2135 if __name__
== '__main__':
2137 # The Subversion test suite code assumes it's being invoked from
2138 # within a working copy of the Subversion sources, and tries to use
2139 # the binaries in that tree. Since the cvs2svn tree never contains
2140 # a Subversion build, we just use the system's installed binaries.
2141 svntest
.main
.svn_binary
= 'svn'
2142 svntest
.main
.svnlook_binary
= 'svnlook'
2143 svntest
.main
.svnadmin_binary
= 'svnadmin'
2144 svntest
.main
.svnversion_binary
= 'svnversion'
2146 svntest
.main
.run_tests(test_list
)