diff --git a/Makefile.in b/Makefile.in index 2d3e67d..8e68ef4 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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) diff --git a/test/system/001-minimal b/test/system/001-minimal new file mode 100755 index 0000000..daa1f73 --- /dev/null +++ b/test/system/001-minimal @@ -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 diff --git a/test/system/002-extended b/test/system/002-extended new file mode 100755 index 0000000..03728c8 --- /dev/null +++ b/test/system/002-extended @@ -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 diff --git a/test/system/003-memlock b/test/system/003-memlock new file mode 100755 index 0000000..0369d1e --- /dev/null +++ b/test/system/003-memlock @@ -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 diff --git a/test/system/004-priority b/test/system/004-priority new file mode 100755 index 0000000..976747b --- /dev/null +++ b/test/system/004-priority @@ -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 diff --git a/test/system/005-scfilter b/test/system/005-scfilter new file mode 100755 index 0000000..285d14d --- /dev/null +++ b/test/system/005-scfilter @@ -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 diff --git a/test/system/006-privdrop b/test/system/006-privdrop new file mode 100755 index 0000000..5756534 --- /dev/null +++ b/test/system/006-privdrop @@ -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 diff --git a/test/system/007-cmdmon b/test/system/007-cmdmon new file mode 100755 index 0000000..f97954a --- /dev/null +++ b/test/system/007-cmdmon @@ -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 diff --git a/test/system/100-clockupdate b/test/system/100-clockupdate new file mode 100755 index 0000000..1d698e1 --- /dev/null +++ b/test/system/100-clockupdate @@ -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 diff --git a/test/system/101-rtc b/test/system/101-rtc new file mode 100755 index 0000000..82c8d51 --- /dev/null +++ b/test/system/101-rtc @@ -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 diff --git a/test/system/102-hwtimestamp b/test/system/102-hwtimestamp new file mode 100755 index 0000000..a09b1d9 --- /dev/null +++ b/test/system/102-hwtimestamp @@ -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 diff --git a/test/system/103-refclock b/test/system/103-refclock new file mode 100755 index 0000000..623b9de --- /dev/null +++ b/test/system/103-refclock @@ -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 diff --git a/test/system/run b/test/system/run new file mode 100755 index 0000000..b31a36c --- /dev/null +++ b/test/system/run @@ -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 ] diff --git a/test/system/test.common b/test/system/test.common new file mode 100644 index 0000000..6a2f697 --- /dev/null +++ b/test/system/test.common @@ -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 +}