blob: 36025442769c36b3167ac8207343fc2ca7c9a850 [file] [log] [blame]
#!/usr/bin/env python
# py:encoding=utf-8
#
# backport_tests.py: Test backport.pl
#
# 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.
######################################################################
# General modules
import contextlib
import functools
import os
import re
import sys
@contextlib.contextmanager
def chdir(dir):
try:
saved_dir = os.getcwd()
os.chdir(dir)
yield
finally:
os.chdir(saved_dir)
# Our testing module
# HACK: chdir to cause svntest.main.svn_binary to be set correctly
sys.path.insert(0, os.path.abspath('../../subversion/tests/cmdline'))
with chdir('../../subversion/tests/cmdline'):
import svntest
# (abbreviations)
Skip = svntest.testcase.Skip_deco
SkipUnless = svntest.testcase.SkipUnless_deco
XFail = svntest.testcase.XFail_deco
Issues = svntest.testcase.Issues_deco
Issue = svntest.testcase.Issue_deco
Wimp = svntest.testcase.Wimp_deco
######################################################################
# Helper functions
BACKPORT_PL = os.path.abspath(os.path.join(os.path.dirname(__file__),
'backport.pl'))
STATUS = 'branch/STATUS'
class BackportTest(object):
"""Decorator. See self.__call__()."""
def __init__(self, uuid):
"""The argument is the UUID embedded in the dump file.
If the argument is None, then there is no dump file."""
self.uuid = uuid
def __call__(self, test_func):
"""Return a decorator that: builds TEST_FUNC's sbox, creates
^/subversion/trunk, and calls TEST_FUNC, then compare its output to the
expected dump file named after TEST_FUNC."""
# .wraps() propagates the wrappee's docstring to the wrapper.
@functools.wraps(test_func)
def wrapped_test_func(sbox):
expected_dump_file = './%s.dump' % (test_func.func_name,)
sbox.build()
# r2: prepare ^/subversion/ tree
sbox.simple_mkdir('subversion', 'subversion/trunk')
sbox.simple_mkdir('subversion/tags', 'subversion/branches')
sbox.simple_move('A', 'subversion/trunk')
sbox.simple_move('iota', 'subversion/trunk')
sbox.simple_commit(message='Create trunk')
# r3: branch
sbox.simple_copy('subversion/trunk', 'branch')
sbox.simple_append('branch/STATUS', '')
sbox.simple_add('branch/STATUS')
sbox.simple_commit(message='Create branch, with STATUS file')
# r4: random change on trunk
sbox.simple_append('subversion/trunk/iota', 'First change\n')
sbox.simple_commit(message='First change')
# r5: random change on trunk
sbox.simple_append('subversion/trunk/A/mu', 'Second change\n')
sbox.simple_commit(message='Second change')
# Do the work.
test_func(sbox)
# Verify it.
verify_backport(sbox, expected_dump_file, self.uuid)
return wrapped_test_func
def make_entry(revisions=None, logsummary=None, notes=None, branch=None, votes=None):
assert revisions
if logsummary is None:
logsummary = "default logsummary"
if votes is None:
votes = {+1 : ['jrandom']}
entry = {
'revisions': revisions,
'logsummary': logsummary,
'notes': notes,
'branch': branch,
'votes': votes,
}
return entry
def serialize_entry(entry):
return ''.join([
# revisions,
' * %s\n'
% (", ".join("r%ld" % revision for revision in entry['revisions'])),
# logsummary
' %s\n' % (entry['logsummary'],),
# notes
' Notes: %s\n' % (entry['notes'],) if entry['notes'] else '',
# branch
' Branch: %s\n' % (entry['branch'],) if entry['branch'] else '',
# votes
' Votes:\n',
''.join(' '
'%s: %s\n' % ({1: '+1', 0: '+0', -1: '-1', -0: '-0'}[vote],
", ".join(entry['votes'][vote]))
for vote in entry['votes']),
'\n', # empty line after entry
])
def serialize_STATUS(approveds,
serialize_entry=serialize_entry):
"""Construct and return the contents of a STATUS file.
APPROVEDS is an iterable of ENTRY dicts. The dicts are defined
to have the following keys: 'revisions', a list of revision numbers (ints);
'logsummary'; and 'votes', a dict mapping ±1/±0 (int) to list of voters.
"""
strings = []
strings.append("Status of 1.8.x:\n\n")
strings.append("Candidate changes:\n")
strings.append("==================\n\n")
strings.append("Random new subheading:\n")
strings.append("======================\n\n")
strings.append("Veto-blocked changes:\n")
strings.append("=====================\n\n")
strings.append("Approved changes:\n")
strings.append("=================\n\n")
strings.extend(map(serialize_entry, approveds))
return "".join(strings)
def run_backport(sbox, error_expected=False, extra_env=[]):
"""Run backport.pl. EXTRA_ENV is a list of key=value pairs (str) to set in
the child's environment. ERROR_EXPECTED is propagated to run_command()."""
# TODO: if the test is run in verbose mode, pass DEBUG=1 in the environment,
# and pass error_expected=True to run_command() to not croak on
# stderr output from the child (because it uses 'sh -x').
args = [
'/usr/bin/env',
'SVN=' + svntest.main.svn_binary,
'YES=1', 'MAY_COMMIT=1', 'AVAILID=jrandom',
] + list(extra_env) + [
'perl', BACKPORT_PL,
]
with chdir(sbox.ospath('branch')):
return svntest.main.run_command(args[0], error_expected, False, *(args[1:]))
def verify_backport(sbox, expected_dump_file, uuid):
"""Compare the contents of the SBOX repository with EXPECTED_DUMP_FILE.
Set the UUID of SBOX to UUID beforehand.
Based on svnsync_tests.py:verify_mirror."""
if uuid is None:
# There is no expected dump file.
return
# Remove some SVNSync-specific housekeeping properties from the
# mirror repository in preparation for the comparison dump.
svntest.actions.enable_revprop_changes(sbox.repo_dir)
for revnum in range(0, 1+int(sbox.youngest())):
svntest.actions.run_and_verify_svnadmin(None, [], [],
"delrevprop", "-r", revnum, sbox.repo_dir, "svn:date")
# Create a dump file from the mirror repository.
dest_dump = open(expected_dump_file).readlines()
svntest.actions.run_and_verify_svnadmin(None, None, [],
'setuuid', '--', sbox.repo_dir, uuid)
src_dump = svntest.actions.run_and_verify_dump(sbox.repo_dir)
svntest.verify.compare_dump_files(
"Dump files", "DUMP", src_dump, dest_dump)
######################################################################
# Tests
#
# Each test must return on success or raise on failure.
#----------------------------------------------------------------------
@BackportTest('76cee987-25c9-4d6c-ad40-000000000001')
def backport_indented_entry(sbox):
"parsing of entries with nonstandard indentation"
# r6: nominate r4
approved_entries = [
make_entry([4]),
]
def reindenting_serialize_entry(*args, **kwargs):
entry = serialize_entry(*args, **kwargs)
return ('\n' + entry).replace('\n ', '\n')[1:]
sbox.simple_append(STATUS, serialize_STATUS(approved_entries,
serialize_entry=reindenting_serialize_entry))
sbox.simple_commit(message='Nominate r4')
# Run it.
run_backport(sbox)
#----------------------------------------------------------------------
@BackportTest('76cee987-25c9-4d6c-ad40-000000000002')
def backport_two_approveds(sbox):
"backport with two approveds"
# r6: Enter votes
approved_entries = [
make_entry([4]),
make_entry([5]),
]
sbox.simple_append(STATUS, serialize_STATUS(approved_entries))
sbox.simple_commit(message='Nominate r4. Nominate r5.')
# r7, r8: Run it.
run_backport(sbox)
# Now back up and do three entries.
# r9: revert r7, r8
svntest.actions.run_and_verify_svnlook(None, ["8\n"], [],
'youngest', sbox.repo_dir)
sbox.simple_update()
svntest.main.run_svn(None, 'merge', '-r8:6',
'^/branch', sbox.ospath('branch'))
sbox.simple_commit(message='Revert the merges.')
# r10: Another change on trunk.
# (Note that this change must be merged after r5.)
sbox.simple_rm('subversion/trunk/A')
sbox.simple_commit(message='Third change on trunk.')
# r11: Nominate r10.
sbox.simple_append(STATUS, serialize_entry(make_entry([10])))
sbox.simple_commit(message='Nominate r10.')
# r12, r13, r14: Run it.
run_backport(sbox)
#----------------------------------------------------------------------
@BackportTest('76cee987-25c9-4d6c-ad40-000000000003')
def backport_accept(sbox):
"test --accept parsing"
# r6: conflicting change on branch
sbox.simple_append('branch/iota', 'Conflicts with first change\n')
sbox.simple_commit(message="Conflicting change on iota")
# r7: nominate r4 with --accept (because of r6)
approved_entries = [
make_entry([4], notes="Merge with --accept=theirs-conflict."),
]
def reindenting_serialize_entry(*args, **kwargs):
entry = serialize_entry(*args, **kwargs)
return ('\n' + entry).replace('\n ', '\n')[1:]
sbox.simple_append(STATUS, serialize_STATUS(approved_entries,
serialize_entry=reindenting_serialize_entry))
sbox.simple_commit(message='Nominate r4')
# Run it.
run_backport(sbox)
#----------------------------------------------------------------------
@BackportTest('76cee987-25c9-4d6c-ad40-000000000004')
def backport_branches(sbox):
"test branches"
# r6: conflicting change on branch
sbox.simple_append('branch/iota', 'Conflicts with first change')
sbox.simple_commit(message="Conflicting change on iota")
# r7: backport branch
sbox.simple_update()
sbox.simple_copy('branch', 'subversion/branches/r4')
sbox.simple_commit(message='Create a backport branch')
# r8: merge into backport branch
sbox.simple_update()
svntest.main.run_svn(None, 'merge', '--record-only', '-c4',
'^/subversion/trunk', sbox.ospath('subversion/branches/r4'))
sbox.simple_mkdir('subversion/branches/r4/A_resolved')
sbox.simple_append('subversion/branches/r4/iota', "resolved\n", truncate=1)
sbox.simple_commit(message='Conflict resolution via mkdir')
# r9: nominate r4 with branch
approved_entries = [
make_entry([4], branch="r4")
]
sbox.simple_append(STATUS, serialize_STATUS(approved_entries))
sbox.simple_commit(message='Nominate r4')
# Run it.
run_backport(sbox)
#----------------------------------------------------------------------
@BackportTest('76cee987-25c9-4d6c-ad40-000000000005')
def backport_multirevisions(sbox):
"test multirevision entries"
# r6: nominate r4,r5
approved_entries = [
make_entry([4,5])
]
sbox.simple_append(STATUS, serialize_STATUS(approved_entries))
sbox.simple_commit(message='Nominate a group.')
# Run it.
run_backport(sbox)
#----------------------------------------------------------------------
@BackportTest(None) # would be 000000000006
def backport_conflicts_detection(sbox):
"test the conflicts detector"
# r6: conflicting change on branch
sbox.simple_append('branch/iota', 'Conflicts with first change\n')
sbox.simple_commit(message="Conflicting change on iota")
# r7: nominate r4, but without the requisite --accept
approved_entries = [
make_entry([4], notes="This will conflict."),
]
sbox.simple_append(STATUS, serialize_STATUS(approved_entries))
sbox.simple_commit(message='Nominate r4')
# Run it.
exit_code, output, errput = run_backport(sbox, True,
# Choose conflicts mode:
["MAY_COMMIT=0"])
# Verify
expected_errput = (
r'(?ms)' # re.MULTILINE | re.DOTALL
r'.*Warning summary.*'
r'^r4 [(]default logsummary[)]: Conflicts on iota.*'
)
expected_errput = svntest.verify.RegexListOutput(
[
r'Warning summary',
r'===============',
r'r4 [(]default logsummary[)]: Conflicts on iota',
],
match_all=False)
svntest.verify.verify_outputs(None, output, errput,
svntest.verify.AnyOutput, expected_errput)
svntest.verify.verify_exit_code(None, exit_code, 1)
#----------------------------------------------------------------------
########################################################################
# Run the tests
# list all tests here, starting with None:
test_list = [ None,
backport_indented_entry,
backport_two_approveds,
backport_accept,
backport_branches,
backport_multirevisions,
backport_conflicts_detection,
# When adding a new test, include the test number in the last
# 6 bytes of the UUID.
]
if __name__ == '__main__':
svntest.main.run_tests(test_list)
# NOTREACHED
### End of file.