blob: 180826aa290f0a7b9ba64a294f44f5d29508ab2a [file] [log] [blame]
#!/usr/bin/env python
#
# svnmucc_tests.py: tests of svnmucc
#
# Subversion is a tool for revision control.
# See http://subversion.apache.org for more information.
#
# ====================================================================
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
######################################################################
import svntest
import re
XFail = svntest.testcase.XFail_deco
Issues = svntest.testcase.Issues_deco
Issue = svntest.testcase.Issue_deco
######################################################################
@Issues(3895,3953)
def reject_bogus_mergeinfo(sbox):
"reject bogus mergeinfo"
sbox.build(create_wc=False)
expected_error = ".*(E200020.*Invalid revision|E175002.*PROPPATCH)"
# At present this tests the server, but if we ever make svnmucc
# validate the mergeinfo up front then it will only test the client
svntest.actions.run_and_verify_svnmucc([], expected_error,
'propset', 'svn:mergeinfo', '/B:0',
'-m', 'log msg',
sbox.repo_url + '/A')
_svnmucc_re = re.compile(b'^(r[0-9]+) committed by jrandom at (.*)$')
_log_re = re.compile('^ ([ADRM] /[^\(]+($| \(from .*:[0-9]+\)$))')
_err_re = re.compile('^svnmucc: (.*)$')
def test_svnmucc(repo_url, expected_path_changes, *varargs):
"""Run svnmucc with the list of SVNMUCC_ARGS arguments. Verify that
its run results in a new commit with 'svn log -rHEAD' changed paths
that match the list of EXPECTED_PATH_CHANGES."""
# First, run svnmucc.
exit_code, outlines, errlines = svntest.main.run_svnmucc('-U', repo_url,
*varargs)
if errlines:
raise svntest.main.SVNCommitFailure(str(errlines))
if len(outlines) != 1 or not _svnmucc_re.match(outlines[0]):
raise svntest.main.SVNLineUnequal(str(outlines))
# Now, run 'svn log -vq -rHEAD'
changed_paths = []
exit_code, outlines, errlines = \
svntest.main.run_svn(None, 'log', '-vqrHEAD', repo_url)
if errlines:
raise svntest.Failure("Unable to verify commit with 'svn log': %s"
% (str(errlines)))
for line in outlines:
match = _log_re.match(line)
if match:
changed_paths.append(match.group(1).rstrip('\n\r'))
expected_path_changes.sort()
changed_paths.sort()
if changed_paths != expected_path_changes:
raise svntest.Failure("Logged path changes differ from expectations\n"
" expected: %s\n"
" actual: %s" % (str(expected_path_changes),
str(changed_paths)))
def xtest_svnmucc(repo_url, expected_errors, *varargs):
"""Run svnmucc with the list of SVNMUCC_ARGS arguments. Verify that
its run results match the list of EXPECTED_ERRORS."""
# First, run svnmucc.
exit_code, outlines, errlines = svntest.main.run_svnmucc('-U', repo_url,
*varargs)
errors = []
for line in errlines:
match = _err_re.match(line)
if match:
errors.append(line.rstrip('\n\r'))
if errors != expected_errors:
raise svntest.main.SVNUnmatchedError(str(errors))
def basic_svnmucc(sbox):
"basic svnmucc tests"
sbox.build()
empty_file = sbox.ospath('empty')
file = sbox.ospath('file')
svntest.main.file_append(empty_file, '')
svntest.main.file_append(file, 'file')
# revision 2
test_svnmucc(sbox.repo_url,
['A /foo'
], # ---------
'-m', 'log msg',
'mkdir', 'foo')
# revision 3
test_svnmucc(sbox.repo_url,
['A /z.c',
], # ---------
'-m', 'log msg',
'put', empty_file, 'z.c')
# revision 4
test_svnmucc(sbox.repo_url,
['A /foo/z.c (from /z.c:3)',
'A /foo/bar (from /foo:3)',
], # ---------
'-m', 'log msg',
'cp', '3', 'z.c', 'foo/z.c',
'cp', '3', 'foo', 'foo/bar')
# revision 5
test_svnmucc(sbox.repo_url,
['A /zig (from /foo:4)',
'D /zig/bar',
'D /foo',
'A /zig/zag (from /foo:4)',
], # ---------
'-m', 'log msg',
'cp', '4', 'foo', 'zig',
'rm', 'zig/bar',
'mv', 'foo', 'zig/zag')
# revision 6
test_svnmucc(sbox.repo_url,
['D /z.c',
'A /zig/zag/bar/y.c (from /z.c:5)',
'A /zig/zag/bar/x.c (from /z.c:3)',
], # ---------
'-m', 'log msg',
'mv', 'z.c', 'zig/zag/bar/y.c',
'cp', '3', 'z.c', 'zig/zag/bar/x.c')
# revision 7
test_svnmucc(sbox.repo_url,
['D /zig/zag/bar/y.c',
'A /zig/zag/bar/y y.c (from /zig/zag/bar/y.c:6)',
'A /zig/zag/bar/y%20y.c (from /zig/zag/bar/y.c:6)',
], # ---------
'-m', 'log msg',
'mv', 'zig/zag/bar/y.c', 'zig/zag/bar/y%20y.c',
'cp', 'HEAD', 'zig/zag/bar/y.c', 'zig/zag/bar/y%2520y.c')
# revision 8
test_svnmucc(sbox.repo_url,
['D /zig/zag/bar/y y.c',
'A /zig/zag/bar/z z1.c (from /zig/zag/bar/y y.c:7)',
'A /zig/zag/bar/z%20z.c (from /zig/zag/bar/y%20y.c:7)',
'A /zig/zag/bar/z z2.c (from /zig/zag/bar/y y.c:7)',
], #---------
'-m', 'log msg',
'mv', 'zig/zag/bar/y%20y.c', 'zig/zag/bar/z z1.c',
'cp', 'HEAD', 'zig/zag/bar/y%2520y.c', 'zig/zag/bar/z%2520z.c',
'cp', 'HEAD', 'zig/zag/bar/y y.c', 'zig/zag/bar/z z2.c')
# revision 9
test_svnmucc(sbox.repo_url,
['D /zig/zag',
'A /zig/foo (from /zig/zag:8)',
'D /zig/foo/bar/z%20z.c',
'D /zig/foo/bar/z z2.c',
'R /zig/foo/bar/z z1.c (from /zig/zag/bar/x.c:6)',
], #---------
'-m', 'log msg',
'mv', 'zig/zag', 'zig/foo',
'rm', 'zig/foo/bar/z z1.c',
'rm', 'zig/foo/bar/z%20z2.c',
'rm', 'zig/foo/bar/z%2520z.c',
'cp', '6', 'zig/zag/bar/x.c', 'zig/foo/bar/z%20z1.c')
# revision 10
test_svnmucc(sbox.repo_url,
['R /zig/foo/bar (from /zig/z.c:9)',
], #---------
'-m', 'log msg',
'rm', 'zig/foo/bar',
'cp', '9', 'zig/z.c', 'zig/foo/bar')
# revision 11
test_svnmucc(sbox.repo_url,
['R /zig/foo/bar (from /zig/foo/bar:9)',
'D /zig/foo/bar/z z1.c',
], #---------
'-m', 'log msg',
'rm', 'zig/foo/bar',
'cp', '9', 'zig/foo/bar', 'zig/foo/bar',
'rm', 'zig/foo/bar/z%20z1.c')
# revision 12
test_svnmucc(sbox.repo_url,
['R /zig/foo (from /zig/foo/bar:11)',
], #---------
'-m', 'log msg',
'rm', 'zig/foo',
'cp', 'head', 'zig/foo/bar', 'zig/foo')
# revision 13
test_svnmucc(sbox.repo_url,
['D /zig',
'A /foo (from /foo:4)',
'A /foo/foo (from /foo:4)',
'A /foo/foo/foo (from /foo:4)',
'D /foo/foo/bar',
'R /foo/foo/foo/bar (from /foo:4)',
], #---------
'-m', 'log msg',
'rm', 'zig',
'cp', '4', 'foo', 'foo',
'cp', '4', 'foo', 'foo/foo',
'cp', '4', 'foo', 'foo/foo/foo',
'rm', 'foo/foo/bar',
'rm', 'foo/foo/foo/bar',
'cp', '4', 'foo', 'foo/foo/foo/bar')
# revision 14
test_svnmucc(sbox.repo_url,
['A /boozle (from /foo:4)',
'A /boozle/buz',
'A /boozle/buz/nuz',
], #---------
'-m', 'log msg',
'cp', '4', 'foo', 'boozle',
'mkdir', 'boozle/buz',
'mkdir', 'boozle/buz/nuz')
# revision 15
test_svnmucc(sbox.repo_url,
['A /boozle/buz/svnmucc-test.py',
'A /boozle/guz (from /boozle/buz:14)',
'A /boozle/guz/svnmucc-test.py',
], #---------
'-m', 'log msg',
'put', empty_file, 'boozle/buz/svnmucc-test.py',
'cp', '14', 'boozle/buz', 'boozle/guz',
'put', empty_file, 'boozle/guz/svnmucc-test.py')
# revision 16
test_svnmucc(sbox.repo_url,
['M /boozle/buz/svnmucc-test.py',
'R /boozle/guz/svnmucc-test.py',
], #---------
'-m', 'log msg',
'put', empty_file, 'boozle/buz/svnmucc-test.py',
'rm', 'boozle/guz/svnmucc-test.py',
'put', empty_file, 'boozle/guz/svnmucc-test.py')
# revision 17
test_svnmucc(sbox.repo_url,
['R /foo/bar (from /foo/foo:16)'
], #---------
'-m', 'log msg',
'rm', 'foo/bar',
'cp', '16', 'foo/foo', 'foo/bar',
'propset', 'testprop', 'true', 'foo/bar')
# revision 18
test_svnmucc(sbox.repo_url,
['M /foo/bar'
], #---------
'-m', 'log msg',
'propdel', 'testprop', 'foo/bar')
# revision 19
test_svnmucc(sbox.repo_url,
['M /foo/z.c',
'M /foo/foo',
], #---------
'-m', 'log msg',
'propset', 'testprop', 'true', 'foo/z.c',
'propset', 'testprop', 'true', 'foo/foo')
# revision 20
test_svnmucc(sbox.repo_url,
['M /foo/z.c',
'M /foo/foo',
], #---------
'-m', 'log msg',
'propsetf', 'testprop', empty_file, 'foo/z.c',
'propsetf', 'testprop', empty_file, 'foo/foo')
# revision 21
test_svnmucc(sbox.repo_url,
['M /foo/z.c',
], #---------
'-m', 'log msg',
'propset', 'testprop', 'false', 'foo/z.c',
'put', file, 'foo/z.c')
# Expected missing revision error
xtest_svnmucc(sbox.repo_url,
["svnmucc: E200004: 'a' is not a revision"
], #---------
'-m', 'log msg',
'cp', 'a', 'b')
# Expected cannot be younger error
xtest_svnmucc(sbox.repo_url,
['svnmucc: E160006: No such revision 42',
], #---------
'-m', 'log msg',
'cp', '42', 'a', 'b')
# Expected already exists error
xtest_svnmucc(sbox.repo_url,
["svnmucc: E160020: Path 'foo' already exists",
], #---------
'-m', 'log msg',
'cp', '17', 'a', 'foo')
# Expected copy_src already exists error
xtest_svnmucc(sbox.repo_url,
["svnmucc: E160020: Path 'a/bar' already exists",
], #---------
'-m', 'log msg',
'cp', '17', 'foo', 'a',
'cp', '17', 'foo/foo', 'a/bar')
# Expected not found error
xtest_svnmucc(sbox.repo_url,
["svnmucc: E160013: Path 'a' not found in revision 17",
], #---------
'-m', 'log msg',
'cp', '17', 'a', 'b')
def propset_root_internal(sbox, target):
## propset on ^/
svntest.actions.run_and_verify_svnmucc(None, [],
'-m', 'log msg',
'propset', 'foo', 'bar',
target)
svntest.actions.run_and_verify_svn('bar', [],
'propget', '--no-newline', 'foo',
target)
## propdel on ^/
svntest.actions.run_and_verify_svnmucc(None, [],
'-m', 'log msg',
'propdel', 'foo',
target)
svntest.actions.run_and_verify_svn([],
'.*W200017: Property.*not found',
'propget', '--no-newline', 'foo',
target)
@Issues(3663)
def propset_root(sbox):
"propset/propdel on repos root"
sbox.build(create_wc=False)
propset_root_internal(sbox, sbox.repo_url)
propset_root_internal(sbox, sbox.repo_url + '/iota')
def too_many_log_messages(sbox):
"test log message mutual exclusivity checks"
sbox.build() # would use read-only=True, but need a place to stuff msg_file
msg_file = sbox.ospath('svnmucc_msg')
svntest.main.file_append(msg_file, 'some log message')
err_msg = ["svnmucc: E205000: --message (-m), --file (-F), and "
"--with-revprop=svn:log are mutually exclusive"]
xtest_svnmucc(sbox.repo_url, err_msg,
'--non-interactive',
'-m', 'log msg',
'-F', msg_file,
'mkdir', 'A/subdir')
xtest_svnmucc(sbox.repo_url, err_msg,
'--non-interactive',
'-m', 'log msg',
'--with-revprop', 'svn:log=proppy log message',
'mkdir', 'A/subdir')
xtest_svnmucc(sbox.repo_url, err_msg,
'--non-interactive',
'-F', msg_file,
'--with-revprop', 'svn:log=proppy log message',
'mkdir', 'A/subdir')
xtest_svnmucc(sbox.repo_url, err_msg,
'--non-interactive',
'-m', 'log msg',
'-F', msg_file,
'--with-revprop', 'svn:log=proppy log message',
'mkdir', 'A/subdir')
@Issues(3418)
def no_log_msg_non_interactive(sbox):
"test non-interactive without a log message"
sbox.build(create_wc=False)
xtest_svnmucc(sbox.repo_url,
["svnmucc: E205001: Cannot invoke editor to get log message "
"when non-interactive"
], #---------
'--non-interactive',
'mkdir', 'A/subdir')
def nested_replaces(sbox):
"nested replaces"
sbox.build(create_wc=False)
repo_url = sbox.repo_url
svntest.actions.run_and_verify_svnmucc(None, [],
'-U', repo_url, '-m', 'r2: create tree',
'rm', 'A',
'rm', 'iota',
'mkdir', 'A', 'mkdir', 'A/B', 'mkdir', 'A/B/C',
'mkdir', 'M', 'mkdir', 'M/N', 'mkdir', 'M/N/O',
'mkdir', 'X', 'mkdir', 'X/Y', 'mkdir', 'X/Y/Z')
svntest.actions.run_and_verify_svnmucc(None, [],
'-U', repo_url, '-m', 'r3: nested replaces',
*("""
rm A rm M rm X
cp HEAD X/Y/Z A cp HEAD A/B/C M cp HEAD M/N/O X
cp HEAD A/B A/B cp HEAD M/N M/N cp HEAD X/Y X/Y
rm A/B/C rm M/N/O rm X/Y/Z
cp HEAD X A/B/C cp HEAD A M/N/O cp HEAD M X/Y/Z
rm A/B/C/Y
""".split()))
# ### TODO: need a smarter run_and_verify_log() that verifies copyfrom
excaped = svntest.main.ensure_list(map(re.escape, [
' R /A (from /X/Y/Z:2)',
' A /A/B (from /A/B:2)',
' R /A/B/C (from /X:2)',
' R /M (from /A/B/C:2)',
' A /M/N (from /M/N:2)',
' R /M/N/O (from /A:2)',
' R /X (from /M/N/O:2)',
' A /X/Y (from /X/Y:2)',
' R /X/Y/Z (from /M:2)',
' D /A/B/C/Y',
]))
expected_output = svntest.verify.UnorderedRegexListOutput(excaped
+ ['^--*', '^r3.*', '^--*', '^Changed paths:',])
svntest.actions.run_and_verify_svn(expected_output, [],
'log', '-qvr3', repo_url)
def prohibited_deletes_and_moves(sbox):
"test prohibited delete and move operations"
# These action sequences were allowed in 1.8.13, but are prohibited in 1.9.x
# and later. Most of them probably indicate an inadvertent user mistake.
# See dev@, 2015-05-11, "Re: Issue 4579 / svnmucc fails to process certain
# deletes", <http://svn.haxx.se/dev/archive-2015-05/0038.shtml>
sbox.build(read_only = True)
svntest.main.file_write(sbox.ospath('file'), "New contents")
xtest_svnmucc(sbox.repo_url,
["svnmucc: E200009: Can't delete node at 'iota'",
], #---------
'-m', 'r2: modify and delete /iota',
'put', sbox.ospath('file'), 'iota',
'rm', 'iota')
xtest_svnmucc(sbox.repo_url,
["svnmucc: E200009: Can't delete node at 'iota'",
], #---------
'-m', 'r2: propset and delete /iota',
'propset', 'prop', 'val', 'iota',
'rm', 'iota')
xtest_svnmucc(sbox.repo_url,
["svnmucc: E160013: Can't delete node at 'iota' as it does "
"not exist",
], #---------
'-m', 'r2: delete and delete /iota',
'rm', 'iota',
'rm', 'iota')
# Subversion 1.8.13 used to move /iota without applying the text change.
xtest_svnmucc(sbox.repo_url,
["svnmucc: E200009: Can't delete node at 'iota'",
], #---------
'-m', 'r2: modify and move /iota',
'put', sbox.ospath('file'), 'iota',
'mv', 'iota', 'iota2')
# Subversion 1.8.13 used to move /A without applying the inner remove.
xtest_svnmucc(sbox.repo_url,
["svnmucc: E200009: Can't delete node at 'A'",
], #---------
'-m', 'r2: delete /A/B and move /A',
'rm', 'A/B',
'mv', 'A', 'A1')
def svnmucc_type_errors(sbox):
"test type errors"
sbox.build(read_only=True)
sbox.simple_append('file', 'New contents')
xtest_svnmucc(sbox.repo_url,
["svnmucc: E160016: Can't operate on 'B' "
"because 'A' is not a directory"],
'-m', '',
'put', sbox.ospath('file'), 'A',
'mkdir', 'A/B',
'propset', 'iota', 'iota', 'iota')
xtest_svnmucc(sbox.repo_url,
["svnmucc: E200009: Can't delete node at 'A'"],
'-m', '',
'mkdir', 'A/Z',
'put', sbox.ospath('file'), 'A')
xtest_svnmucc(sbox.repo_url,
["svnmucc: E160020: Path 'Z' already exists, or was created "
"by an earlier operation"],
'-m', '',
'mkdir', 'A/Z',
'put', sbox.ospath('file'), 'A/Z')
def svnmucc_propset_and_put(sbox):
"propset and put"
sbox.build()
sbox.simple_append('file', 'New contents')
# First in the sane order: put, then propset
xtest_svnmucc(sbox.repo_url,
[],
'-m', '',
'put', sbox.ospath('file'), 't1',
'propset', 't1', 't1', 't1')
# And now in an impossible order: propset, then put
xtest_svnmucc(sbox.repo_url,
["svnmucc: E200009: Can't set properties at not existing 't2'"],
'-m', '',
'propset', 't2', 't2', 't2',
'put', sbox.ospath('file'), 't2')
# And if the target already exists (dir)
xtest_svnmucc(sbox.repo_url,
["svnmucc: E200009: Can't delete node at 'A'"],
'-m', '',
'propset', 'A', 'A', 'A',
'put', sbox.ospath('file'), 'A')
# And if the target already exists (file) # fixed in r1702467
xtest_svnmucc(sbox.repo_url,
[],
'-m', '',
'propset', 'iota', 'iota', 'iota',
'put', sbox.ospath('file'), 'iota')
# Put same file twice (non existing)
xtest_svnmucc(sbox.repo_url,
["svnmucc: E160020: Path 't3' already exists, or was created "
"by an earlier operation"],
'-m', '',
'put', sbox.ospath('file'), 't3',
'put', sbox.ospath('file'), 't3')
# Put same file twice (existing)
xtest_svnmucc(sbox.repo_url,
["svnmucc: E200009: Can't update file at 't1'"],
'-m', '',
'put', sbox.ospath('file'), 't1',
'put', sbox.ospath('file'), 't1')
######################################################################
test_list = [ None,
reject_bogus_mergeinfo,
basic_svnmucc,
propset_root,
too_many_log_messages,
no_log_msg_non_interactive,
nested_replaces,
prohibited_deletes_and_moves,
svnmucc_type_errors,
svnmucc_propset_and_put,
]
if __name__ == '__main__':
svntest.main.run_tests(test_list)