test: add system tests

Add a new set of tests for testing basic functionality, starting chronyd
with root privileges on the actual system instead of the simulator.

Tests numbered in the 100-199 range are considered destructive and
intended to be used only on machines dedicated for development or
testing. They are started by the run script only with the -d option.
They may adjust/step the system clock and other clocks, block the RTC,
enable HW timestamping, create SHM segments, etc.

Other tests should not interfere with the system and should work even
when another NTP server/client is running.
This commit is contained in:
Miroslav Lichvar 2019-04-17 17:33:16 +02:00
parent 69c6dffd63
commit 6e52a9be7a
14 changed files with 638 additions and 0 deletions

View file

@ -113,10 +113,12 @@ install-docs :
quickcheck : chronyd chronyc
$(MAKE) -C test/unit check
cd test/simulation && ./run
cd test/system && ./run
check : chronyd chronyc
$(MAKE) -C test/unit check
cd test/simulation && ./run -i 20 -m 2
cd test/system && ./run
print-chronyd-objects :
@echo $(OBJS) $(EXTRA_OBJS)

13
test/system/001-minimal Executable file
View file

@ -0,0 +1,13 @@
#!/bin/bash
. ./test.common
test_start "minimal configuration"
minimal_config=1
start_chronyd || test_fail
stop_chronyd || test_fail
check_chronyd_messages || test_fail
test_pass

13
test/system/002-extended Executable file
View file

@ -0,0 +1,13 @@
#!/bin/bash
. ./test.common
test_start "extended configuration"
start_chronyd || test_fail
wait_for_sync || test_fail
stop_chronyd || test_fail
check_chronyd_messages || test_fail
check_chronyd_files || test_fail
test_pass

15
test/system/003-memlock Executable file
View file

@ -0,0 +1,15 @@
#!/bin/bash
. ./test.common
test_start "memory locking"
extra_chronyd_options="-m"
start_chronyd || test_fail
wait_for_sync || test_fail
stop_chronyd || test_fail
check_chronyd_messages || test_fail
check_chronyd_files || test_fail
test_pass

15
test/system/004-priority Executable file
View file

@ -0,0 +1,15 @@
#!/bin/bash
. ./test.common
test_start "process priority"
extra_chronyd_options="-P 1"
start_chronyd || test_fail
wait_for_sync || test_fail
stop_chronyd || test_fail
check_chronyd_messages || test_fail
check_chronyd_files || test_fail
test_pass

17
test/system/005-scfilter Executable file
View file

@ -0,0 +1,17 @@
#!/bin/bash
. ./test.common
check_chronyd_features SCFILTER || test_skip "SCFILTER support disabled"
test_start "system call filter"
for extra_chronyd_options in "-F -1" "-F 1"; do
start_chronyd || test_fail
wait_for_sync || test_fail
stop_chronyd || test_fail
check_chronyd_messages || test_fail
check_chronyd_files || test_fail
done
test_pass

17
test/system/006-privdrop Executable file
View file

@ -0,0 +1,17 @@
#!/bin/bash
. ./test.common
check_chronyd_features PRIVDROP || test_skip "PRIVDROP support disabled"
user="nobody"
test_start "dropping of root privileges"
minimal_config=1
start_chronyd || test_fail
stop_chronyd || test_fail
check_chronyd_messages || test_fail
test_pass

69
test/system/007-cmdmon Executable file
View file

@ -0,0 +1,69 @@
#!/bin/bash
. ./test.common
test_start "chronyc commands"
start_chronyd || test_fail
for command in \
"accheck 1.2.3.4" \
"delete $server" \
"add server $server" \
"deny" \
"allow" \
"burst 1/1" \
"clients" \
"cmdallow 1.2.3.4" \
"cmdaccheck 1.2.3.4" \
"cmddeny 1.2.3.4" \
"cyclelogs" \
"dfreq 1.0e-3" \
"doffset -0.1" \
"dump" \
"local off" \
"local" \
"manual on" \
"settime now" \
"manual delete 0" \
"settime now" \
"manual reset" \
"manual off" \
"maxdelay $server 1e-2" \
"maxdelaydevratio $server 5.0" \
"maxdelayratio $server 3.0" \
"maxpoll $server 5" \
"maxupdateskew $server 10.0" \
"minpoll $server 3" \
"minstratum $server 1" \
"ntpdata $server" \
"offline" \
"online" \
"onoffline" \
"polltarget $server 10" \
"refresh" \
"rekey" \
"reselect" \
"reselectdist 1e-3" \
"serverstats" \
"smoothtime reset" \
"smoothtime activate" \
"shutdown" \
; do
run_chronyc "$command" || test_fail
done
stop_chronyd || test_fail
check_chronyd_messages || test_fail
start_chronyd || test_fail
run_chronyc "makestep" && test_fail
check_chronyc_output "500 Failure" || test_fail
run_chronyc "trimrtc" && test_fail
check_chronyc_output "513 RTC driver not running" || test_fail
run_chronyc "writertc" && test_fail
check_chronyc_output "513 RTC driver not running" || test_fail
stop_chronyd || test_fail
test_pass

30
test/system/100-clockupdate Executable file
View file

@ -0,0 +1,30 @@
#!/bin/bash
. ./test.common
test_start "update of system clock"
clock_control=1
minimal_config=1
start_chronyd || test_fail
run_chronyc "dfreq 1e-3" || test_fail
check_chronyc_output "200 OK" || test_fail
before=$(date '+%s')
run_chronyc "doffset -1.0" || test_fail
check_chronyc_output "200 OK" || test_fail
run_chronyc "makestep" || test_fail
check_chronyc_output "200 OK" || test_fail
after=$(date '+%s')
test_message 1 0 "checking system clock"
[ "$before" -lt "$after" ] && test_ok || test_bad || test_fail
run_chronyc "doffset 1.0" || test_fail
run_chronyc "makestep" || test_fail
stop_chronyd || test_fail
check_chronyd_messages || test_fail
check_chronyd_message_count "System clock was stepped by" 2 2 || test_fail
test_pass

19
test/system/101-rtc Executable file
View file

@ -0,0 +1,19 @@
#!/bin/bash
. ./test.common
check_chronyd_features RTC || test_skip "RTC support disabled"
[ -c "/dev/rtc" ] || test_skip "missing /dev/rtc"
test_start "real-time clock"
minimal_config=1
extra_chronyd_options="-s"
extra_chronyd_directives="rtcfile $TEST_DIR/rtcfile"
echo "1 $(date +%s) 0.0 0.0" > "$TEST_DIR/rtcfile"
start_chronyd || test_fail
stop_chronyd || test_fail
check_chronyd_message_count "\(clock off from RTC\|RTC time before last\)" 1 1 || test_fail
test_pass

28
test/system/102-hwtimestamp Executable file
View file

@ -0,0 +1,28 @@
#!/bin/bash
. ./test.common
[ "$(uname -s)" = "Linux" ] || test_skip "non-Linux system"
hwts_iface=""
for iface_path in /sys/class/net/*; do
iface=$(basename "$iface_path")
if ethtool -T "$iface" 2> /dev/null | grep -q HWTSTAMP_FILTER_ALL; then
hwts_iface="$iface"
break
fi
done
[ -n "$hwts_iface" ] || test_skip "no HW timestamping interface found"
test_start "hardware timestamping"
minimal_config=1
extra_chronyd_directives="hwtimestamp $hwts_iface"
start_chronyd || test_fail
stop_chronyd || test_fail
check_chronyd_messages || test_fail
check_chronyd_message_count "Enabled HW timestamping on $hwts_iface" 1 1 || test_fail
test_pass

19
test/system/103-refclock Executable file
View file

@ -0,0 +1,19 @@
#!/bin/bash
. ./test.common
check_chronyd_features REFCLOCK || test_skip "refclock support disabled"
test_start "reference clocks"
extra_chronyd_directives="
refclock SOCK $TEST_DIR/refclock.sock
refclock SHM 100"
start_chronyd || test_fail
wait_for_sync || test_fail
stop_chronyd || test_fail
check_chronyd_messages || test_fail
check_chronyd_files || test_fail
test_pass

64
test/system/run Executable file
View file

@ -0,0 +1,64 @@
#!/bin/bash
print_help() {
echo "$1 [-a] [-d] [TEST]..."
}
run_test() {
local result name=$1
if [ $destructive -ne 1 ] && [[ "$name" == 1[0-9][0-9]-* ]]; then
echo "SKIP (destructive test)"
return 9
fi
./$name
result=$?
if [ $result -ne 0 -a $result -ne 9 ]; then
if [ $abort_on_fail -ne 0 ]; then
exit 1
fi
fi
return $result
}
passed=() failed=() skipped=()
abort_on_fail=0
destructive=0
while getopts ":ad" opt; do
case $opt in
a) abort_on_fail=1;;
d) destructive=1;;
*) print_help "$0"; exit 3;;
esac
done
shift $[$OPTIND - 1]
[ $# -gt 0 ] && tests=($@) || tests=([0-9]*-*[^_])
for test in "${tests[@]}"; do
printf "%s " "$test"
run_test $test
result=$?
echo
case $result in
0) passed=(${passed[@]} $test);;
9) skipped=(${skipped[@]} $test);;
*) failed=(${failed[@]} $test);;
esac
done
echo
echo "SUMMARY:"
echo " TOTAL $[${#passed[@]} + ${#failed[@]} + ${#skipped[@]}]"
echo " PASSED ${#passed[@]}"
echo " FAILED ${#failed[@]} (${failed[@]})"
echo " SKIPPED ${#skipped[@]} (${skipped[@]})"
[ ${#failed[@]} -eq 0 ]

317
test/system/test.common Normal file
View file

@ -0,0 +1,317 @@
# Copyright (C) Miroslav Lichvar 2009
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
export LC_ALL=C
export PATH=${CHRONY_PATH:-../..}:$PATH
export TEST_DIR=$(pwd)/tmp
test_start() {
check_chronyd_features NTP CMDMON || test_skip "NTP/CMDMON support disabled"
id -u "$user" > /dev/null 2> /dev/null || test_skip "missing user $user"
mkdir -p "$TEST_DIR" && chmod 700 "$TEST_DIR" && chown "$user:$(id -g "$user")" "$TEST_DIR" || \
test_skip "could not create $TEST_DIR"
rm -f "$TEST_DIR"/*
echo "Testing $*:"
}
test_pass() {
echo "PASS"
exit 0
}
test_fail() {
echo "FAIL"
exit 1
}
test_skip() {
local msg=$1
[ -n "$msg" ] && echo "SKIP ($msg)" || echo "SKIP"
exit 9
}
test_ok() {
pad_line
echo -e "\tOK"
return 0
}
test_bad() {
pad_line
echo -e "\tBAD"
return 1
}
test_error() {
pad_line
echo -e "\tERROR"
return 1
}
chronyd=$(command -v chronyd)
chronyc=$(command -v chronyc)
[ $EUID -eq 0 ] || test_skip "not root"
[ -x "$chronyd" ] || test_skip "chronyd not found"
[ -x "$chronyc" ] || test_skip "chronyc not found"
netstat -aln > /dev/null 2> /dev/null || test_skip "missing netstat"
# Default test testings
default_minimal_config=0
default_extra_chronyd_directives=""
default_extra_chronyd_options=""
default_clock_control=0
default_server=127.0.0.1
default_user=root
# Initialize test settings from their defaults
for defoptname in ${!default_*}; do
optname=${defoptname#default_}
[ -z "${!optname}" ] && declare "$optname"="${!defoptname}"
done
msg_length=0
pad_line() {
local line_length=56
[ $msg_length -lt $line_length ] && \
printf "%$((line_length - msg_length))s" ""
msg_length=0
}
# Print aligned message
test_message() {
local level=$1 eol=$2
shift 2
local msg="$*"
while [ "$level" -gt 0 ]; do
echo -n " "
level=$((level - 1))
msg_length=$((msg_length + 2))
done
echo -n "$msg"
msg_length=$((msg_length + ${#msg}))
if [ "$eol" -ne 0 ]; then
echo
msg_length=0
fi
}
# Check if chronyd has specified features
check_chronyd_features() {
local feature features
features=$($chronyd -v | sed 's/.*(\(.*\)).*/\1/')
for feature; do
echo "$features" | grep -q "+$feature" || return 1
done
}
# Print test settings which differ from default value
print_nondefaults() {
local defoptname optname
test_message 1 1 "non-default settings:"
for defoptname in ${!default_*}; do
optname=${defoptname#default_}
[ "${!defoptname}" = "${!optname}" ] || \
test_message 2 1 "$optname"=${!optname}
done
}
get_conffile() {
echo "$TEST_DIR/chronyd.conf"
}
get_pidfile() {
echo "$TEST_DIR/chronyd.pid"
}
get_logfile() {
echo "$TEST_DIR/chronyd.log"
}
get_cmdsocket() {
echo "$TEST_DIR/chronyd.sock"
}
# Find a free port in the 10000-20000 range (their use is racy)
get_free_port() {
local port
while true; do
port=$((RANDOM % 10000 + 10000))
netstat -aln | grep '^udp.*:'$port && continue
break
done
echo $port
}
generate_chrony_conf() {
local ntpport cmdport
ntpport=$(get_free_port)
cmdport=$(get_free_port)
echo "0.0 10000" > "$TEST_DIR/driftfile"
echo "1 MD5 abcdefghijklmnopq" > "$TEST_DIR/keys"
echo "0.0" > "$TEST_DIR/tempcomp"
(
echo "pidfile $(get_pidfile)"
echo "bindcmdaddress $(get_cmdsocket)"
echo "port $ntpport"
echo "cmdport $cmdport"
echo "$extra_chronyd_directives"
[ "$minimal_config" -ne 0 ] && exit 0
echo "allow"
echo "cmdallow"
echo "local"
echo "server $server port $ntpport minpoll -6 maxpoll -6"
[ "$server" = "127.0.0.1" ] && echo "bindacqaddress $server"
echo "bindaddress 127.0.0.1"
echo "bindcmdaddress 127.0.0.1"
echo "dumpdir $TEST_DIR"
echo "logdir $TEST_DIR"
echo "log tempcomp rawmeasurements refclocks statistics tracking rtc"
echo "logbanner 0"
echo "smoothtime 100.0 0.001"
echo "include /dev/null"
echo "keyfile $TEST_DIR/keys"
echo "driftfile $TEST_DIR/driftfile"
echo "tempcomp $TEST_DIR/tempcomp 0.1 0 0 0 0"
) > "$(get_conffile)"
}
get_chronyd_options() {
[ "$clock_control" -eq 0 ] && echo "-x"
echo "-l $(get_logfile)"
echo "-f $(get_conffile)"
echo "-u $user"
echo "$extra_chronyd_options"
}
# Start a chronyd instance
start_chronyd() {
local pid pidfile=$(get_pidfile)
print_nondefaults
test_message 1 0 "starting chronyd"
generate_chrony_conf
trap stop_chronyd EXIT
$CHRONYD_WRAPPER "$chronyd" $(get_chronyd_options)
[ $? -eq 0 ] && [ -f "$pidfile" ] && ps -p "$(cat "$pidfile")" > /dev/null && test_ok || test_error
}
wait_for_sync() {
test_message 1 0 "waiting for synchronization"
sleep 1 && test_ok || test_error
}
# Stop the chronyd instance
stop_chronyd() {
local pid pidfile
pidfile=$(get_pidfile)
[ -f "$pidfile" ] || return 0
pid=$(cat "$pidfile")
test_message 1 0 "stopping chronyd"
if ! kill "$pid"; then
test_error
return
fi
# Wait for the process to terminate (we cannot use "wait")
while ps -p "$pid" > /dev/null; do
sleep 0.1
done
test_ok
}
# Check chronyd log for expected and unexpected messages
check_chronyd_messages() {
local logfile=$(get_logfile)
test_message 1 0 "checking chronyd messages"
grep -q 'chronyd exiting' "$logfile" && \
([ "$clock_control" -eq 0 ] || ! grep -q 'Disabled control of system clock' "$logfile") && \
([ "$clock_control" -ne 0 ] || grep -q 'Disabled control of system clock' "$logfile") && \
([ "$minimal_config" -ne 0 ] || grep -q 'Frequency .* read from' "$logfile") && \
grep -q 'chronyd exiting' "$logfile" && \
! grep -q 'Could not' "$logfile" && \
! grep -q 'Disabled command socket' "$logfile" && \
test_ok || test_bad
}
# Check the number of messages matching a pattern in a specified file
check_chronyd_message_count() {
local count pattern=$1 min=$2 max=$3 logfile=$(get_logfile)
test_message 1 0 "checking message \"$pattern\""
count=$(grep "$pattern" "$(get_logfile)" | wc -l)
[ "$min" -le "$count" ] && [ "$count" -le "$max" ] && test_ok || test_bad
}
# Check the logs and dump file for measurements and a clock update
check_chronyd_files() {
test_message 1 0 "checking chronyd files"
grep -q " $server .* 111 111 1111 " "$TEST_DIR/measurements.log" && \
grep -q " $server " "$TEST_DIR/statistics.log" && \
grep -q " $server " "$TEST_DIR/tracking.log" && \
[ -f "$TEST_DIR/tempcomp.log" ] && [ "$(wc -l < "$TEST_DIR/tempcomp.log")" -ge 2 ] && \
[ -f "$TEST_DIR/$server.dat" ] && [ "$(wc -l < "$TEST_DIR/$server.dat")" -ge 5 ] && \
test_ok || test_bad
}
# Run a chronyc command
run_chronyc() {
test_message 1 0 "running chronyc $*"
"$chronyc" -h "$(get_cmdsocket)" -n -m "$@" > "$TEST_DIR/chronyc.out" && test_ok || test_error
}
# Compare chronyc output with specified pattern
check_chronyc_output() {
local pattern=$1
test_message 1 0 "checking chronyc output"
[[ "$(cat "$TEST_DIR/chronyc.out")" =~ $pattern ]] && test_ok || test_bad
}