2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-09-06 00:45:23 +00:00
Files
kea/src/lib/testutils/dhcp_test_lib.sh.in

645 lines
21 KiB
Bash
Raw Normal View History

# Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# A list of Kea processes, mainly used by the cleanup functions.
KEA_PROCS="kea-dhcp4 kea-dhcp6 kea-dhcp-ddns"
### Logging functions ###
# Prints error message.
test_lib_error() {
local s=${1} # Error message.
local no_new_line=${2} # If specified, the message not terminated with
# new line.
printf "ERROR/test_lib: %s" "${s}"
if [ -z ${no_new_line} ]; then
printf "%s" "\n"
fi
}
# Prints info message.
test_lib_info() {
local s=${1} # Info message.
local no_new_line=${2} # If specified, the message is not terminated with
# new line.
printf "INFO/test_lib: %s" "${s}"
if [ -z ${no_new_line} ]; then
printf "%s" "\n"
fi
}
### Assertions ###
# Assertion that checks if two numbers are equal.
# If numbers are not equal, the mismatched values are presented and the
# detailed error is printed. The detailed error must use the printf
# formatting like this:
# "Expected that some value 1 %d is equal to some other value %d".
assert_eq() {
val1=${1} # Reference value
val2=${2} # Tested value
detailed_err=${3} # Detailed error format string
# If nothing found, present an error an exit.
if [ ${val1} -ne ${val2} ]; then
printf "Assertion failure: ${val1} != ${val2}, for val1=${val1}, val2=${val2}\n"
printf "${detailed_err}\n" ${val1} ${val2}
clean_exit 1
fi
}
# Assertion that checks if two strings are equal.
# If numbers are not equal, the mismatched values are presented and the
# detailed error is printed. The detailed error must use the printf
# formatting like this:
# "Expected that some value 1 %d is equal to some other value %d".
assert_str_eq() {
val1=${1} # Reference value
val2=${2} # Tested value
detailed_err=${3} # Detailed error format string
# If nothing found, present an error an exit.
if [ "${val1}" != "${val2}" ]; then
printf "Assertion failure: ${val1} != ${val2}, for val1=${val1}, val2=${val2}\n"
printf "${detailed_err}\n" ${val1} ${val2}
clean_exit 1
fi
}
# Assertion that checks if one string contains another string.
# If assertion fails, both strings are displayed and the detailed
# error is printed. The detailed error must use the printf formatting
# like this:
# "Expected some string to contain this string: %s".
assert_string_contains() {
pattern="${1}" # Substring or awk pattern
text="${2}" # Text to be searched for substring
detailed_err="${3}" # Detailed error format string
# Search for a pattern
match=$( printf "%s" "${text}" | awk /"${pattern}"/ )
# If nothing found, present an error and exit.
if [ -z "${match}" ]; then
printf "Assertion failure: \n\"%s\"\n\ndoesn't contain pattern:\n
\"%s\"\n\n" "${text}" "${pattern}"
printf "${detailed_err}\n" "\"${pattern}\""
clean_exit 1
fi
}
# Begins a test by prining its name.
test_start() {
TEST_NAME=${1}
if [ -z ${TEST_NAME} ]; then
test_lib_error "test_start requires test name as an argument"
clean_exit 1
fi
printf "\nSTART TEST ${TEST_NAME}\n"
}
# Prints test result an cleans up after the test.
test_finish() {
local exit_code=${1} # Exit code to be returned by the exit function.
if [ ${exit_code} -eq 0 ]; then
cleanup
printf "PASSED ${TEST_NAME}\n\n"
else
# Dump log file for debugging purposes if specified and exists.
# Otherwise the code below would simply call cat.
if [ -n "${LOG_FILE}" -a -s "${LOG_FILE}" ]; then
printf "Log file dump:\n"
cat ${LOG_FILE}
fi
cleanup
printf "FAILED ${TEST_NAME}\n\n"
fi
}
# Stores the configuration specified as a parameter in the configuration
# file which name has been set in the ${CFG_FILE} variable.
create_config() {
local cfg="${1}" # Configuration string.
if [ -z ${CFG_FILE} ]; then
test_lib_error "create_config requires CFG_FILE variable be set"
clean_exit 1
elif [ -z "${cfg}" ]; then
test_lib_error "create_config requires argument holding a configuration"
clean_exit 1
fi
printf "Creating Kea configuration file: %s.\n" ${CFG_FILE}
printf "%b" ${cfg} > ${CFG_FILE}
}
# Stores the keactrl configuration specified as a parameter in the
# configuration file which name has been set in the ${KEACTRL_CFG_FILE}
# variable.
create_keactrl_config() {
local cfg="${1}" # Configuration string.
if [ -z ${KEACTRL_CFG_FILE} ]; then
test_lib_error "create_keactrl_config requires KEACTRL_CFG_FILE \
variable be set"
clean_exit 1
elif [ -z "${cfg}" ]; then
test_lib_error "create_keactrl_config requires argument holding a \
configuration"
clean_exit 1
fi
printf "Creating keactrl configuration file: %s.\n" ${KEACTRL_CFG_FILE}
printf "%b" ${cfg} > ${KEACTRL_CFG_FILE}
}
# Sets Kea logger to write to the file specified by the global value
# ${LOG_FILE}.
set_logger() {
if [ -z ${LOG_FILE} ]; then
test_lib_error "set_logger requies LOG_FILE variable be set"
clean_exit 1
fi
printf "Kea log will be stored in %s.\n" ${LOG_FILE}
export KEA_LOGGER_DESTINATION=${LOG_FILE}
}
# Returns the number of running process pids and the list of pids.
# Return values:
# _GET_PIDS: holds space separated list of pids.
# _GET_PIDS_NUM: holds the number pids.
get_pids() {
local proc_name=${1} # Process name
if [ -z ${proc_name} ]; then
test_lib_error "get_pids requires process name"
clean_exit 1
fi
_GET_PIDS=$( ps axwwo pid,command | grep ${proc_name} \
| grep -v grep | awk '{print $1}' )
_GET_PIDS_NUM=$( printf "%s" "${_GET_PIDS}" | wc -w | awk '{print $1}' )
}
# Returns the number of occurrences of the Kea log message in the log file.
# Return value:
# _GET_LOG_MESSAGES: number of log message occurrences.
get_log_messages() {
local msg="${1}" # Message id, e.g. DHCP6_SHUTDOWN
if [ -z ${msg} ]; then
test_lib_error "get_log_messages require message identifier"
clean_exit 1
fi
_GET_LOG_MESSAGES=0
# If log file is not present, the number of occurrences is 0.
if [ -s ${LOG_FILE} ]; then
# Grep log file for the logger message occurrences and remove
# whitespaces, if any.
_GET_LOG_MESSAGES=$( grep -o ${msg} ${LOG_FILE} | wc -w | tr -d " ")
fi
}
# Returns the number of server configurations performed so far. Also
# returns the number of configuration errors.
# Return values:
# _GET_RECONFIGS: number of configurations so far.
# _GET_RECONFIG_ERRORS: number of configuration errors.
get_reconfigs() {
# Grep log file for CONFIG_COMPLETE occurences. There should
# be one occurence per (re)configuration.
_GET_RECONFIGS=$( grep -o CONFIG_COMPLETE ${LOG_FILE} | wc -w )
# Grep log file for CONFIG_LOAD_FAIL to check for configuration
# failures.
_GET_RECONFIG_ERRORS=$( grep -o CONFIG_LOAD_FAIL ${LOG_FILE} | wc -w )
# Remove whitespaces
${_GET_RECONFIGS##*[! ]}
${_GET_RECONFIG_ERRORS##*[! ]}
}
# Performs cleanup after test.
# It shuts down running Kea processes and removes temporary files.
# The location of the log file and the configuration files should be set
# in the ${LOG_FILE}, ${CFG_FILE} and ${KEACTRL_CFG_FILE} variables
# recpectively, prior to calling this function.
cleanup() {
# If there is no KEA_PROCS set, just return
if [ -z "${KEA_PROCS}" ]; then
return
fi
# KEA_PROCS holds the name of all Kea processes. Shut down each
# of them if running.
for proc_name in ${KEA_PROCS}
do
get_pids ${proc_name}
# Shut down running Kea processes.
for pid in ${_GET_PIDS}
do
printf "Shutting down Kea proccess having pid %d.\n" ${pid}
kill -9 ${pid}
done
done
# Remove temporary files.
rm -rf ${LOG_FILE}
# Use asterisk to remove all files starting with the given name,
# in case the LFC has been run. LFC creates files with postfixes
# appended to the lease file name.
if [ ! -z "${LEASE_FILE}" ]; then
rm -rf ${LEASE_FILE}*
fi
rm -rf ${CFG_FILE}
rm -rf ${KEACTRL_CFG_FILE}
}
# Exists the test in the clean way.
# It peformes the cleanup and prints whether the test has passed or failed.
# If a test fails, the Kea log is dumped.
clean_exit() {
2014-05-28 13:08:14 +02:00
exit_code=${1} # Exit code to be returned by the exit function.
case ${exit_code} in
''|*[!0-9]*)
test_lib_error "argument passed to clean_exit must be a number" ;;
esac
# Print test result and perform a cleanup
test_finish ${exit_code}
2014-05-28 13:08:14 +02:00
exit ${exit_code}
}
# Starts Kea process in background using a configuration file specified
# in the global variable ${CFG_FILE}.
start_kea() {
local bin=${1}
if [ -z ${bin} ]; then
test_lib_error "binary name must be specified for start_kea"
clean_exit 1
fi
printf "Running command %s.\n" "\"${bin} -c ${CFG_FILE}\""
${bin} -c ${CFG_FILE} &
}
# Waits with timeout for Kea to start.
# This function repeatedly checs if the Kea log file has been created
# and is non-empty. If it is, the function assumes that Kea has started.
2014-05-28 13:08:14 +02:00
# It doesn't check the contents of the log file though.
# If the log file doesn't exist the function sleeps for a second and
# checks again. This is repeated until timeout is reached or non-empty
# log file is found. If timeout is reached, the function reports an
# error.
# Return value:
# _WAIT_FOR_KEA: 0 if Kea hasn't started, 1 otherwise
wait_for_kea() {
local timeout=${1} # Desired timeout in seconds.
case ${timeout} in
''|*[!0-9]*)
test_lib_error "argument passed to wait_for_kea must be a number"
clean_exit 1 ;;
esac
local loops=0 # Loops counter
_WAIT_FOR_KEA=0
test_lib_info "wait_for_kea " "skip-new-line"
while [ ! -s ${LOG_FILE} ] && [ ${loops} -le ${timeout} ]; do
printf "."
sleep 1
loops=$( expr $loops + 1 )
done
printf "\n"
if [ ${loops} -le ${timeout} ]; then
_WAIT_FOR_KEA=1
fi
}
# Waits for a specific message to occur in the Kea log file.
# This function is called when the test expects specific message
# to show up in the log file as a result of some action that has
# been taken. Typically, the test expects that the message
# is logged when the SIGHUP or SIGTERM signal has been sent to the
# Kea process.
# This function waits a specified number of seconds for the number
# of message occurrences to show up. If the expected number of
# message doesn't occur, the error status is returned.
# Return value:
# _WAIT_FOR_MESSAGE: 0 if the message hasn't occured, 1 otherwise.
wait_for_message() {
local timeout=${1} # Expected timeout value in seconds.
local message="${2}" # Expected message id.
local occurrences=${3} # Number of expected occurrences.
# Validate timeout
case ${timeout} in
''|*[!0-9]*)
test_lib_error "argument timeout passed to wait_for_message must \
be a number"
clean_exit 1 ;;
esac
# Validate message
if [ -z ${message} ]; then
test_lib_error "message id is a required argument for wait_for_message"
clean_exit 1
fi
# Validate occurrences
case ${occurrences} in
''|*[!0-9]*)
test_lib_error "argument occurrences passed to wait_for_message \
must be a number"
clean_exit 1 ;;
esac
local loops=0 # Number of loops performed so far.
_WAIT_FOR_MESSAGE=0
test_lib_info "wait_for_message ${message}: " "skip-new-line"
# Check if log file exists and if we reached timeout.
while [ ${loops} -le ${timeout} ]; do
printf "."
# Check if the message has been logged.
get_log_messages ${message}
if [ ${_GET_LOG_MESSAGES} -ge ${occurrences} ]; then
printf "\n"
2014-05-28 13:08:14 +02:00
_WAIT_FOR_MESSAGE=1
return
fi
# Message not recorded. Keep going.
sleep 1
loops=$( expr ${loops} + 1 )
done
printf "\n"
# Timeout.
}
# Waits for server to be down.
# Return value:
# _WAIT_FOR_SERVER_DOWN: 1 if server is down, 0 if timeout occured and the
# server is still running.
wait_for_server_down() {
local timeout=${1} # Timeout specified in seconds.
local proc_name=${2} # Server process name.
case ${timeout} in
''|*[!0-9]*)
test_lib_error "argument passed to wait_for_server_down must be a number"
clean_exit 1 ;;
esac
local loops=0 # Loops counter
_WAIT_FOR_SERVER_DOWN=0
test_lib_info "wait_for_server_down ${proc_name}: " "skip-new-line"
while [ ${loops} -le ${timeout} ]; do
printf "."
get_pids ${proc_name}
if [ ${_GET_PIDS_NUM} -eq 0 ]; then
printf "\n"
_WAIT_FOR_SERVER_DOWN=1
return
fi
sleep 1
loops=$( expr $loops + 1 )
done
printf "\n"
}
# Sends specified signal to the Kea process.
send_signal() {
local sig=${1} # Signal number.
local proc_name=${2} # Process name
# Validate signal
case ${sig} in
''|*[!0-9]*)
test_lib_error "signal number passed to send_signal \
must be a number"
clean_exit 1 ;;
esac
# Validate process name
if [ -z ${proc_name} ]; then
test_lib_error "send_signal requires process name be passed as argument"
clean_exit 1
fi
# Get Kea pid.
get_pids ${proc_name}
if [ ${_GET_PIDS_NUM} -ne 1 ]; then
printf "ERROR: expected one Kea process to be started.\
Found %d processes started.\n" ${_GET_PIDS_NUM}
clean_exit 1
2014-05-28 13:08:14 +02:00
fi
printf "Sending signal ${sig} to Kea process (pid=%s).\n" ${_GET_PIDS}
# Actually send a signal.
kill -${sig} ${_GET_PIDS}
}
[3769] Added env var,KEA_PIDFILE_DIR; D2 now uses a PIDFile src/lib/dhcpsrv/daemon.c/h Daemon::Daemon() - Constructor will now override the default PID directory with the value of env variable KEA_PIDFILE_DIR. This provides a simple means to alter the value for tests. Added am_file_author_ flag so Daemon instances will only delete a file they have written. src/lib/testutils/dhcp_test_lib.sh.in - verify_server_pid() - new function which verifies that a server has a PID file and that it contains the server's PID, and that the process is alive. src/bin/keactrl/tests/Makefile.am - added export of KEA_PIDFILE_DIR to override default PID directory during tests Added PID file creation to D2 src/bin/d2/d_controller.cc - DControllerBase::launch() - Added block to createPIDFile() -DControllerBase::parseArgs() Replaced call to Daemon::init() with call to Daemon::setConfigFile() src/bin/d2/tests/Makefile.am - added export of KEA_PIDFILE_DIR to override default PID directory during tests src/bin/d2/tests/d2_process_tests.sh.in - dupcliate_server_start_test() - new test which verifies that D2 cannot be started twice (with the same configuration file) src/bin/d2/tests/d2_unittests.cc - main(int argc, char* argv[]) sets environment variable KEA_PIDFILE_DIR to override default PID diretory during tests src/lib/util/pid_file.cc/h src/lib/util/tests/pid_file_unittest.cc Changed PIDFile::check() to return either the PID contained in the PID file if the process is alive, or 0, rather than bool. This permits callers to see/log the PID.
2015-07-02 14:42:58 -04:00
# Verifies that a server is up running by its PID file
# The PID file is constructed from the given config file name and
# binary name. If it exists and the PID it contains refers to a
# live process it sets _SERVER_PID_FILE and _SERVER_PID to the
# corresponding values. Otherwise, it emits an error and exits.
verify_server_pid() {
local bin_name="${1}" # binary name of the server
local cfg_file="${2}" # config file name
# We will construct the PID file name based on the server config
# and binary name
if [ -z ${bin_name} ]; then
test_lib_error "verify_server_pid requires binary name"
clean_exit 1
fi
if [ -z ${cfg_file} ]; then
test_lib_error "verify_server_pid requires config file name"
clean_exit 1
fi
# Only the file name portion of the config file is used, try and
[3769] Added env var,KEA_PIDFILE_DIR; D2 now uses a PIDFile src/lib/dhcpsrv/daemon.c/h Daemon::Daemon() - Constructor will now override the default PID directory with the value of env variable KEA_PIDFILE_DIR. This provides a simple means to alter the value for tests. Added am_file_author_ flag so Daemon instances will only delete a file they have written. src/lib/testutils/dhcp_test_lib.sh.in - verify_server_pid() - new function which verifies that a server has a PID file and that it contains the server's PID, and that the process is alive. src/bin/keactrl/tests/Makefile.am - added export of KEA_PIDFILE_DIR to override default PID directory during tests Added PID file creation to D2 src/bin/d2/d_controller.cc - DControllerBase::launch() - Added block to createPIDFile() -DControllerBase::parseArgs() Replaced call to Daemon::init() with call to Daemon::setConfigFile() src/bin/d2/tests/Makefile.am - added export of KEA_PIDFILE_DIR to override default PID directory during tests src/bin/d2/tests/d2_process_tests.sh.in - dupcliate_server_start_test() - new test which verifies that D2 cannot be started twice (with the same configuration file) src/bin/d2/tests/d2_unittests.cc - main(int argc, char* argv[]) sets environment variable KEA_PIDFILE_DIR to override default PID diretory during tests src/lib/util/pid_file.cc/h src/lib/util/tests/pid_file_unittest.cc Changed PIDFile::check() to return either the PID contained in the PID file if the process is alive, or 0, rather than bool. This permits callers to see/log the PID.
2015-07-02 14:42:58 -04:00
# extract it. NOTE if this "algorithm" changes this code will need
# to be updated.
fname=`basename ${cfg_file}`
fname=`echo $fname | cut -f1 -d'.'`
if [ -z ${fname} ]; then
test_lib_error "verify_server_pid could not extract config name"
clean_exit 1
fi
# Now we can build the name:
pid_file="$KEA_PIDFILE_DIR/$fname.$bin_name.pid"
if [ ! -e ${pid_file} ]; then
printf "ERROR: PID file:[%s] does not exist\n" ${pid_file}
clean_exit 1
fi
# File exists, does its PID point to a live process?
pid=`cat ${pid_file}`
kill -0 ${pid}
if [ $? -ne 0 ]; then
printf "ERROR: PID file:[%s] exists but PID:[%d] does not\n" \
${pid_file} ${pid}
clean_exit 1
fi
# Make the values accessible to the caller
_SERVER_PID="${pid}"
_SERVER_PID_FILE="${pid_file}"
}
# This test verifies that the binary is reporting its version properly.
version_test() {
test_name=${1} # Test name
# Log the start of the test and print test name.
test_start ${test_name}
# Remove dangling Kea instances and remove log files.
cleanup
REPORTED_VERSION="`${bin_path}/${bin} -v`"
if test "${REPORTED_VERSION}" == "${EXPECTED_VERSION}"; then
test_finish 0
else
printf "ERROR: Expected version ${EXPECTED_VERSION}, got ${REPORTED_VERSION}\n"
test_finish 1
fi
}
# This test verifies that the server is using logger variable
# KEA_LOCKFILE_DIR properly (it should be used to point out to the directory,
# where lockfile should be created. Also, "none" value means to not create
# the lockfile at all).
logger_vars_test() {
test_name=${1} # Test name
# Log the start of the test and print test name.
test_start ${test_name}
# Remove dangling Kea instances and remove log files.
cleanup
# Create bogus configuration file. We don't really want the server to start,
# just want it to log something and die. Empty config is an easy way to
# enforce that behavior.
create_config "{ }"
printf "Please ignore any config error messages.\n"
# Remember old KEA_LOCKFILE_DIR
KEA_LOCKFILE_DIR_OLD=${KEA_LOCKFILE_DIR}
# Set lockfile directory to current directory.
KEA_LOCKFILE_DIR=.
# Start Kea.
start_kea ${bin_path}/${bin}
# Wait for Kea to process the invalid configuration and die.
sleep 1
# Check if it is still running. It should have terminated.
get_pids ${bin}
if [ ${_GET_PIDS_NUM} -ne 0 ]; then
printf "ERROR: expected Kea process to not start. Found %d processes"
printf " running.\n" ${_GET_PIDS_NUM}
# Revert to the old KEA_LOCKFILE_DIR value
KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
clean_exit 1
fi
if [ ! -f "./logger_lockfile" ]; then
printf "ERROR: Expect ${bin} to create logger_lockfile in the\n"
printf "current directory, but no such file exists.\n"
# Revert to the old KEA_LOCKFILE_DIR value
KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR__OLD}
clean_exit 1
fi
# Remove the lock file
rm -f ./logger_lockfile
# Tell Kea to NOT create logfiles at all
KEA_LOCKFILE_DIR="none"
# Start Kea.
start_kea ${bin_path}/${bin}
# Wait for Kea to process the invalid configuration and die.
sleep 1
# Check if it is still running. It should have terminated.
get_pids ${bin}
if [ ${_GET_PIDS_NUM} -ne 0 ]; then
printf "ERROR: expected Kea process to not start. Found %d processes"
printf " running.\n" ${_GET_PIDS_NUM}
# Revert to the old KEA_LOCKFILE_DIR value
KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
clean_exit 1
fi
if [ -f "./logger_lockfile" ]; then
printf "ERROR: Expect ${bin} to NOT create logger_lockfile in the\n"
printf "current directory, but the file exists."
# Revert to the old KEA_LOCKFILE_DIR value
KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
clean_exit 1
fi
# Revert to the old KEA_LOCKFILE_DIR value
printf "Reverting KEA_LOCKFILE_DIR to ${KEA_LOCKFILE_DIR_OLD}\n"
KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
test_finish 0
}
# This test verifies server PID file management
# 1. It verifies that upon startup, the server creates a PID file
# 2. It verifies the an attempt to start a second instance fails
# due to pre-existing PID File/PID detection
server_pid_file_test() {
local server_cfg="${1}"
local log_id="${2}"
# Log the start of the test and print test name.
test_start "${bin}.server_pid_file_test"
# Remove dangling DHCP4 instances and remove log files.
cleanup
# Create new configuration file.
create_config "${CONFIG}"
# Instruct server to log to the specific file.
set_logger
# Start server
start_kea ${bin_path}/${bin}
# Wait up to 20s for server to start.
wait_for_kea 20
if [ ${_WAIT_FOR_KEA} -eq 0 ]; then
printf "ERROR: timeout waiting for %s to start.\n" ${bin}
clean_exit 1
fi
# Verify server is still running
verify_server_pid ${bin} ${CFG_FILE}
printf "PID file is [%s], PID is [%d]" ${_SERVER_PID_FILE} ${_SERVER_PID}
# Now try to start a second one
start_kea ${bin_path}/${bin}
wait_for_message 10 "${log_id}" 1
if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
printf "ERROR: Second %s instance started? PID conflict not reported.\n" ${bin}
clean_exit 1
fi
# Verify server is still running
verify_server_pid ${bin} ${CFG_FILE}
# All ok. Shut down the server and exit.
test_finish 0
}