blob: 7bc5e1196d09c38a52b8de5efd1a12a80ddc150f [file] [log] [blame]
#!/bin/bash
#
# 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.
#
# Script to automate testing of an svnsync master/slave
# configuration. Commits to the slave should write through
# to the master, and the master's post-commit hook svnsync's
# to the slave. The test should be able to throw all kinds
# of svn operations at one or the other, and master/slave
# verified as identical in the end.
#
# Master / slave setup is achieved in a single httpd process
# using virtual hosts bound to different addresses on the
# loopback network (127.0.0.1, 127.0.0.2) for slave and
# master, respectively.
#
# The set of changes sent through the system is currently
# just the test case for issue 2939, using svnmucc
# https://issues.apache.org/jira/browse/SVN-2939
# But of course, any svn traffic liable to break over
# mirroring would be a good addition.
#
# Most of the httpd setup was lifted from davautocheck.sh.
# The common boilerplate snippets to setup/start/stop httpd
# between the two could be factored out and shared.
#
SCRIPTDIR=$(dirname $0)
SCRIPT=$(basename $0)
trap stop_httpd_and_die SIGHUP SIGTERM SIGINT
# Ensure the server uses a known locale.
LC_ALL=C
export LC_ALL
function stop_httpd_and_die() {
[ -e "$HTTPD_PID" ] && kill $(cat "$HTTPD_PID")
exit 1
}
function say() {
echo "$SCRIPT: $*"
}
function fail() {
say "FAIL: " $*
stop_httpd_and_die
}
function get_loadmodule_config() {
local SO="$($APXS -q LIBEXECDIR)/$1.so"
# shared object module?
if [ -r "$SO" ]; then
local NM=$(echo "$1" | sed 's|mod_\(.*\)|\1_module|')
echo "LoadModule $NM \"$SO\"" &&
return
fi
# maybe it's built-in?
"$HTTPD" -l | grep -q "$1\\.c" && return
return 1
}
# Check apxs's SBINDIR and BINDIR for given program names
function get_prog_name() {
for prog in $*
do
for dir in $($APXS -q SBINDIR) $($APXS -q BINDIR)
do
if [ -e "$dir/$prog" ]; then
echo "$dir/$prog" && return
fi
done
done
return 1
}
# splat out httpd config
function setup_config() {
say "setting up config: " $1
cat > "$1" <<__EOF__
$LOAD_MOD_MPM
$LOAD_MOD_LOG_CONFIG
$LOAD_MOD_MIME
$LOAD_MOD_UNIXD
$LOAD_MOD_DAV
LoadModule dav_svn_module "$MOD_DAV_SVN"
$LOAD_MOD_AUTH
$LOAD_MOD_AUTHN_CORE
$LOAD_MOD_AUTHN_FILE
$LOAD_MOD_PROXY
$LOAD_MOD_PROXY_HTTP
$LOAD_MOD_AUTHZ_CORE
$LOAD_MOD_AUTHZ_USER
$LOAD_MOD_AUTHZ_HOST
__EOF__
if "$HTTPD" -v | grep '/2\.[012]' >/dev/null; then
cat >> "$1" <<__EOF__
LockFile lock
User $(id -un)
Group $(id -gn)
__EOF__
else
HTTPD_LOCK="$HTTPD_ROOT/lock"
mkdir "$HTTPD_LOCK" \
|| fail "couldn't create lock directory '$HTTPD_LOCK'"
cat >> "$1" <<__EOF__
# worker and prefork MUST have a mpm-accept lockfile in 2.3.0+
<IfModule worker.c>
Mutex "file:$HTTPD_LOCK" mpm-accept
</IfModule>
<IfModule prefork.c>
Mutex "file:$HTTPD_LOCK" mpm-accept
</IfModule>
__EOF__
fi
cat >> "$1" <<__EOF__
Listen ${TEST_PORT}
ServerName localhost
PidFile "${HTTPD_ROOT}/pid"
LogFormat "%h %l %u %t \"%r\" %>s %b" common
CustomLog "${HTTPD_ROOT}/access_log" common
ErrorLog "${HTTPD_ROOT}/error_log"
LogLevel Debug
ServerRoot "${HTTPD_ROOT}"
DocumentRoot "${HTTPD_ROOT}"
CoreDumpDirectory "${HTTPD_ROOT}"
TypesConfig "${HTTPD_ROOT}/mime.types"
StartServers 4
MaxRequestsPerChild 0
<IfModule worker.c>
ThreadsPerChild 8
</IfModule>
<IfModule event.c>
ThreadsPerChild 8
</IfModule>
MaxClients 16
HostNameLookups Off
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" format
CustomLog "${HTTPD_ROOT}/req" format
CustomLog "${HTTPD_ROOT}/ops" "%t %u %{SVN-REPOS-NAME}e %{SVN-ACTION}e" env=SVN-ACTION
<Directory />
AllowOverride none
</Directory>
<Directory "${HTTPD_ROOT}">
AllowOverride none
#Require all granted
</Directory>
# slave
<VirtualHost ${SLAVE_HOST}>
ServerName ${SLAVE_HOST}
CustomLog "${HTTPD_ROOT}/slave_access_log" common
ErrorLog "${HTTPD_ROOT}/slave_error_log"
# slave 'normal' location
<Location "/${SLAVE_LOCATION}">
DAV svn
SVNPath "${SLAVE_REPOS}"
SVNMasterURI "${MASTER_URL}"
AuthType Basic
AuthName "Subversion Repository"
AuthUserFile ${HTTPD_ROOT}/users
Require valid-user
</Location>
# slave 'sync' location
<Location "/${SYNC_LOCATION}">
DAV svn
SVNPath "${SLAVE_REPOS}"
AuthType Basic
AuthName "Slave Sync Repository"
AuthUserFile ${HTTPD_ROOT}/users
Require valid-user
</Location>
</VirtualHost>
# master
<VirtualHost ${MASTER_HOST}>
ServerName ${MASTER_HOST}>
CustomLog "${HTTPD_ROOT}/master_access_log" common
ErrorLog "${HTTPD_ROOT}/master_error_log"
<Location "/${MASTER_LOCATION}">
DAV svn
SVNPath "${MASTER_REPOS}"
AuthType Basic
AuthName "Subversion Repository"
AuthUserFile ${HTTPD_ROOT}/users
Require valid-user
</Location>
</VirtualHost>
__EOF__
}
function usage() {
echo "usage: $SCRIPT <test-work-directory>" 1>&2
echo " e.g. \"$SCRIPT /tmp/test-work\"" 1>&2
echo
echo " " '<test-work-directory>' must not exist, \
I will not clobber it for you 1>&2
exit 1
}
### Start execution here ###
SCRIPT=$(basename $0)
NO_TESTS=
if [ "x$1" = 'x--no-tests' ]; then
NO_TESTS=1
shift
fi
if [ $# -ne 1 ] ; then
usage
fi
# httpd ServerRoot, all test and runtime artifacts below here
# verify that this doesn't already exist - don't clobber
HTTPD_ROOT=$1
if [ -e "$HTTPD_ROOT" ] ; then
say "ERROR: test work directory $HTTPD_ROOT already exists, please remove" 1>&2
usage
fi
#set -e
# Don't assume sbin is in the PATH.
PATH="$PATH:/usr/sbin:/usr/local/sbin"
# Pick up value from environment or PATH (also try apxs2 - for Debian)
[ ${APXS:+set} ] \
|| APXS=$(which apxs) \
|| APXS=$(which apxs2) \
|| fail "neither apxs or apxs2 found - required to run $SCRIPT"
[ -x $APXS ] || fail "Can't execute apxs executable $APXS"
say APXS: $APXS
if [ -x subversion/svn/svn ]; then
ABS_BUILDDIR=$(pwd)
elif [ -x $SCRIPTDIR/../../svn/svn ]; then
pushd $SCRIPTDIR/../../../ >/dev/null
ABS_BUILDDIR=$(pwd)
popd >/dev/null
else
fail "Run this script from the root of Subversion's build tree!"
fi
# find all our needed executables, in WC or via apxs
httpd="$($APXS -q PROGNAME)"
HTTPD=$(get_prog_name $httpd) || fail "HTTPD not found"
HTPASSWD=$(get_prog_name htpasswd htpasswd2) \
|| fail "Could not find htpasswd or htpasswd2"
SVN=$ABS_BUILDDIR/subversion/svn/svn
SVNADMIN=$ABS_BUILDDIR/subversion/svnadmin/svnadmin
SVNSYNC=$ABS_BUILDDIR/subversion/svnsync/svnsync
SVNMUCC=$ABS_BUILDDIR/subversion/svnmucc/svnmucc
SVNLOOK=$ABS_BUILDDIR/subversion/svnlook/svnlook
[ -x $HTTPD ] || fail "HTTPD '$HTTPD' not executable"
[ -x $HTPASSWD ] \
|| fail "HTPASSWD '$HTPASSWD' not executable"
[ -x $SVN ] || fail "SVN $SVN not built"
[ -x $SVNADMIN ] || fail "SVNADMIN $SVNADMIN not built"
[ -x $SVNSYNC ] || fail "SVNSYNC $SVNSYNC not built"
[ -x $SVNLOOK ] || fail "SVNLOOK $SVNLOOK not built"
[ -x $SVNMUCC ] || fail "SVNMUCC $SVNMUCC not built"
say HTTPD: $HTTPD
say SVN: $SVN
say SVNADMIN: $SVNADMIN
say SVNSYNC: $SVNSYNC
say SVNLOOK: $SVNLOOK
say SVNMUCC: $SVNMUCC
LOAD_MOD_DAV=$(get_loadmodule_config mod_dav) \
|| fail "DAV module not found"
LOAD_MOD_LOG_CONFIG=$(get_loadmodule_config mod_log_config) \
|| fail "log_config module not found"
# proxy needed for svnsync mirroring
LOAD_MOD_PROXY=$(get_loadmodule_config mod_proxy) \
|| fail "proxy module not found"
LOAD_MOD_PROXY_HTTP=$(get_loadmodule_config mod_proxy_http) \
|| fail "proxy_http module not found"
# needed for TypesConfig
LOAD_MOD_MIME=$(get_loadmodule_config mod_mime) \
|| fail "MIME module not found"
# needed for Auth*, Require, etc. directives
LOAD_MOD_AUTH=$(get_loadmodule_config mod_auth) \
|| {
say "Monolithic Auth module not found. Assuming we run against Apache 2.1+"
LOAD_MOD_AUTH="$(get_loadmodule_config mod_auth_basic)" \
|| fail "Auth_Basic module not found."
LOAD_MOD_ACCESS_COMPAT="$(get_loadmodule_config mod_access_compat)" \
&& {
say "Found modules for Apache 2.3.0+"
LOAD_MOD_AUTHN_CORE="$(get_loadmodule_config mod_authn_core)" \
|| fail "Authn_Core module not found."
LOAD_MOD_AUTHZ_CORE="$(get_loadmodule_config mod_authz_core)" \
|| fail "Authz_Core module not found."
LOAD_MOD_AUTHZ_HOST="$(get_loadmodule_config mod_authz_host)" \
|| fail "Authz_Host module not found."
LOAD_MOD_UNIXD=$(get_loadmodule_config mod_unixd) \
|| fail "UnixD module not found"
}
LOAD_MOD_AUTHN_FILE="$(get_loadmodule_config mod_authn_file)" \
|| fail "Authn_File module not found."
LOAD_MOD_AUTHZ_USER="$(get_loadmodule_config mod_authz_user)" \
|| fail "Authz_User module not found."
}
if [ ${APACHE_MPM:+set} ]; then
LOAD_MOD_MPM=$(get_loadmodule_config mod_mpm_$APACHE_MPM) \
|| fail "MPM module not found"
fi
if [ ${MODULE_PATH:+set} ]; then
MOD_DAV_SVN="$MODULE_PATH/mod_dav_svn.so"
MOD_AUTHZ_SVN="$MODULE_PATH/mod_authz_svn.so"
else
MOD_DAV_SVN="$ABS_BUILDDIR/subversion/mod_dav_svn/.libs/mod_dav_svn.so"
MOD_AUTHZ_SVN="$ABS_BUILDDIR/subversion/mod_authz_svn/.libs/mod_authz_svn.so"
fi
[ -r "$MOD_DAV_SVN" ] \
|| fail "dav_svn_module not found, please use '--enable-shared --enable-dso --with-apxs' with your 'configure' script"
[ -r "$MOD_AUTHZ_SVN" ] \
|| fail "authz_svn_module not found, please use '--enable-shared --enable-dso --with-apxs' with your 'configure' script"
export LD_LIBRARY_PATH="$ABS_BUILDDIR/subversion/libsvn_ra_neon/.libs:$ABS_BUILDDIR/subversion/libsvn_ra_local/.libs:$ABS_BUILDDIR/subversion/libsvn_ra_svn/.libs:$LD_LIBRARY_PATH"
MASTER_REPOS="${MASTER_REPOS:-"$HTTPD_ROOT/master_repos"}"
SLAVE_REPOS="${SLAVE_REPOS:-"$HTTPD_ROOT/slave_repos"}"
MASTER_HOST=127.0.0.2
SLAVE_HOST=127.0.0.1
#TEST_PORT=11111
TEST_PORT=$(($RANDOM+1024))
# location directive elements for master,slave,sync
# tests currently work if master==slave,fail if different
# ** Should different locations for each work?
#MASTER_LOCATION="master"
#SLAVE_LOCATION="slave"
MASTER_LOCATION="repo"
SLAVE_LOCATION="repo"
SYNC_LOCATION="sync"
MASTER_URL="http://${MASTER_HOST}:${TEST_PORT}/${MASTER_LOCATION}"
SLAVE_URL="http://${SLAVE_HOST}:${TEST_PORT}/${SLAVE_LOCATION}"
SYNC_URL="http://${SLAVE_HOST}:${TEST_PORT}/${SYNC_LOCATION}"
BASE_URL="$SLAVE_URL"
# setup server and repositories
say "setting up in ${HTTPD_ROOT}:"
mkdir -p $HTTPD_ROOT || fail "cannot mkdir $HTTPD_ROOT"
HTTPD_CONFIG=$HTTPD_ROOT/cfg
setup_config $HTTPD_CONFIG
touch $HTTPD_ROOT/mime.types
HTTPD_USERS="$HTTPD_ROOT/users"
$HTPASSWD -bc $HTTPD_USERS jrandom rayjandom
$HTPASSWD -b $HTTPD_USERS jconstant rayjandom
$HTPASSWD -b $HTTPD_USERS scm scm
$HTPASSWD -b $HTTPD_USERS svnsync svnsync
$SVNADMIN create "$MASTER_REPOS" || fail "create master repos failed"
$SVNADMIN create "$SLAVE_REPOS" || fail "create slave repos failed"
# dup them
$SVNADMIN dump "$MASTER_REPOS" | $SVNADMIN load "$SLAVE_REPOS" \
|| fail "duplicate repositories failed"
# make sure uuid's match
read MASTER_UUID < "$MASTER_REPOS/db/uuid"
read SLAVE_UUID < "$SLAVE_REPOS/db/uuid"
[ "$SLAVE_UUID" = "$MASTER_UUID" ] \
|| fail "master/slave uuid mismatch"
# setup hooks:
# slave allows revprop changes
# master syncs changes to slave
echo "#!/bin/sh" > "$SLAVE_REPOS/hooks/pre-revprop-change"
echo "#!/bin/sh" > "$MASTER_REPOS/hooks/post-revprop-change"
echo "#!/bin/sh" > "$MASTER_REPOS/hooks/post-commit"
echo "$SVNSYNC --non-interactive sync '$SYNC_URL' --username=svnsync --password=svnsync" \
>> "$MASTER_REPOS/hooks/post-revprop-change"
echo "$SVNSYNC --non-interactive sync '$SYNC_URL' --username=svnsync --password=svnsync" \
>> "$MASTER_REPOS/hooks/post-commit"
chmod 0755 "$SLAVE_REPOS/hooks/pre-revprop-change"
chmod 0755 "$MASTER_REPOS/hooks/post-revprop-change"
chmod 0755 "$MASTER_REPOS/hooks/post-commit"
say "created master and slave repositories"
# test config
$HTTPD -f $HTTPD_CONFIG -t || fail "httpd config failure in $HTTPD_CONFIG"
# start httpd
echo -n "${SCRIPT}: starting httpd: "
$HTTPD -f $HTTPD_CONFIG -k start || fail "httpd start failed"
echo "."
say initializing svnsync to $SYNC_URL
HTTPD_PID=$HTTPD_ROOT/pid
$SVNSYNC initialize --non-interactive "$SYNC_URL" "$MASTER_URL" \
--username=svnsync --password=svnsync \
|| fail "svnsync initialize failed"
if [ $NO_TESTS ]; then
echo "MASTER_URL=$MASTER_URL"
echo "SLAVE_URL=$SLAVE_URL"
exit
fi
# OK, let's start testing! Commit changes to slave, expect
# them to proxy through to the master, and then
# svnsync back to the slave
#
# reproducible test case from:
# https://issues.apache.org/jira/browse/SVN-2939
#
BASE_URL="$SLAVE_URL"
say running svnmucc test to $BASE_URL
svnmucc="$SVNMUCC --non-interactive --username jrandom --password rayjandom -mm"
$svnmucc mkdir "$BASE_URL/trunk" mkdir "$BASE_URL/trunk/dir1" mkdir "$BASE_URL/trunk/dir1/dir2"
$svnmucc rm "$BASE_URL/trunk/dir1/dir2"
$svnmucc cp 2 "$BASE_URL/trunk" "$BASE_URL/branch" put /dev/null "$BASE_URL/branch/dir1/dir2"
$svnmucc rm "$BASE_URL/branch" cp 2 "$BASE_URL/trunk" "$BASE_URL/branch" put /dev/null "$BASE_URL/branch/dir1/dir2"
say "svn log on $BASE_URL : "
$SVN --username jrandom --password rayjandom log -vq "$BASE_URL"
# verify result: should be at rev 4 in both repos
# FIXME: do more rigorous verification here
MASTER_HEAD=`$SVNLOOK youngest "$MASTER_REPOS"`
SLAVE_HEAD=`$SVNLOOK youngest "$SLAVE_REPOS"`
say checking consistency of master, slave repositories:
if [ "$MASTER_HEAD" != "4" ] || [ "$SLAVE_HEAD" != "4" ] ;
then
say FAIL: master, slave are at rev $MASTER_HEAD, $SLAVE_HEAD, not 4
say server may be started/stopped manually with:
say " $HTTPD -f $HTTPD_CONFIG -k start|stop"
fail charred remains in $HTTPD_ROOT for your perusal
fi
say "PASS: master, slave are both at r4, as expected"
# The following test case is for the regression issue triggered by r917523.
# The revision r917523 do some url encodings to the paths and uris which are
# not url-encoded. But there is one additional url-encoding of an uri which is
# already encoded. With this extra encoding, committing a path to slave which
# has space in it fails. Please see this thread
# http://svn.haxx.se/dev/archive-2011-03/0641.shtml for more info.
say "Test case for regression issue triggered by r917523"
$svnmucc cp 2 "$BASE_URL/trunk" "$BASE_URL/branch new"
$svnmucc put /dev/null "$BASE_URL/branch new/file" \
--config-option servers:global:http-library=neon
RETVAL=$?
if [ $RETVAL -eq 0 ] ; then
say "PASS: committing a path which has space in it passes"
else
say "FAIL: committing a path which has space in it fails as there are extra
url-encodings happening in server side"
fi
# Test case for commit to out-dated(though target path is up to date) slave.
# See issue #3860 for details.
say "Test case for out-dated slave commit"
svn="$SVN --non-interactive --username=jrandom --password=rayjandom"
# Make a working copy of the slave.
$svn checkout $SLAVE_URL $HTTPD_ROOT/wc
cd $HTTPD_ROOT/wc
# Add a new file named newfile and commit it.
touch branch/newfile
$svn add branch/newfile
$svn commit -mm
say "De-activating post-commit hook on $MASTER_REPOS to make $SLAVE_REPOS go out of sync"
mv "$MASTER_REPOS/hooks/post-commit" "$MASTER_REPOS/hooks/post-commit_"
echo "Change made to file in branch" > $HTTPD_ROOT/wc/branch/newfile
$svn ci -m "Commit from slave"
MASTER_HEAD=`$SVNLOOK youngest "$MASTER_REPOS"`
SLAVE_HEAD=`$SVNLOOK youngest "$SLAVE_REPOS"`
say "Now the slave is at r$SLAVE_HEAD and master is at r$MASTER_HEAD."
# Now any other commit operation will fail with an out-of-date error
$svn cp -m "Creating a branch" ^/trunk ^/branch/newbranch --config-option "servers:global:http-library=neon"
RETVAL=$?
if [ $RETVAL -eq 0 ]; then
say "PASS: Commits succeed even with an out-of-date slave"
else
say "FAIL: Commits fail with an out-of-date slave"
fi
say "Some house-keeping..."
say "Re-activating the post-commit hook on the master repo: $MASTER_REPOS."
mv "$MASTER_REPOS/hooks/post-commit_" "$MASTER_REPOS/hooks/post-commit"
say "Syncing slave with master."
$SVNSYNC --non-interactive sync "$SYNC_URL" --username=svnsync --password=svnsync
# shut it down
echo -n "${SCRIPT}: stopping httpd: "
$HTTPD -f $HTTPD_CONFIG -k stop
echo "."
exit 0