| # 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 |
| |
| TEST_DIR=${TEST_DIR:-$(pwd)/tmp} |
| TEST_LIBDIR=${TEST_LIBDIR:-$TEST_DIR} |
| TEST_LOGDIR=${TEST_LOGDIR:-$TEST_DIR} |
| TEST_RUNDIR=${TEST_RUNDIR:-$TEST_DIR} |
| TEST_SCFILTER=${TEST_SCFILTER:-0} |
| |
| test_start() { |
| check_chronyd_features NTP CMDMON || test_skip "NTP/CMDMON support disabled" |
| |
| [ "${#TEST_DIR}" -ge 5 ] || test_skip "invalid TEST_DIR" |
| |
| rm -rf "$TEST_DIR" |
| mkdir -p "$TEST_DIR" && chmod 700 "$TEST_DIR" || test_skip "could not create $TEST_DIR" |
| |
| [ -d "$TEST_LIBDIR" ] || test_skip "missing $TEST_LIBDIR" |
| [ -d "$TEST_LOGDIR" ] || test_skip "missing $TEST_LOGDIR" |
| [ -d "$TEST_RUNDIR" ] || test_skip "missing $TEST_RUNDIR" |
| |
| rm -f "$TEST_LIBDIR"/* "$TEST_LOGDIR"/* "$TEST_RUNDIR"/* |
| |
| if [ "$user" != "root" ]; then |
| id -u "$user" > /dev/null 2> /dev/null || test_skip "missing user $user" |
| chown "$user:$(id -g "$user")" "$TEST_DIR" || test_skip "could not chown $TEST_DIR" |
| su "$user" -s /bin/sh -c "touch $TEST_DIR/test" 2> /dev/null || \ |
| test_skip "$user cannot access $TEST_DIR" |
| rm "$TEST_DIR/test" |
| fi |
| |
| 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" |
| |
| if netstat -aln > /dev/null 2> /dev/null; then |
| port_list_command="netstat -aln" |
| elif ss -atun > /dev/null 2> /dev/null; then |
| port_list_command="ss -atun" |
| else |
| test_skip "missing netstat or ss" |
| fi |
| |
| # 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_server_name=127.0.0.1 |
| default_server_options="" |
| 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_RUNDIR/chronyd.pid" |
| } |
| |
| get_logfile() { |
| echo "$TEST_LOGDIR/chronyd.log" |
| } |
| |
| get_cmdsocket() { |
| echo "$TEST_RUNDIR/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)) |
| $port_list_command | grep -q '^\(tcp\|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_LIBDIR/driftfile" |
| echo "1 MD5 abcdefghijklmnopq" > "$TEST_DIR/keys" |
| chown "$user:$(id -g "$user")" "$TEST_LIBDIR/driftfile" "$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_name port $ntpport minpoll -6 maxpoll -6 $server_options" |
| |
| [ "$server" = "127.0.0.1" ] && echo "bindacqaddress $server" |
| echo "bindaddress 127.0.0.1" |
| echo "bindcmdaddress 127.0.0.1" |
| echo "dumpdir $TEST_RUNDIR" |
| echo "logdir $TEST_LOGDIR" |
| echo "log tempcomp rawmeasurements refclocks statistics tracking rtc" |
| echo "logbanner 0" |
| echo "smoothtime 100.0 0.001" |
| echo "leapsectz right/UTC" |
| echo "dscp 46" |
| |
| echo "include /dev/null" |
| echo "keyfile $TEST_DIR/keys" |
| echo "driftfile $TEST_LIBDIR/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 "-F $TEST_SCFILTER" |
| 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 |
| |
| rm -f "$TEST_LOGDIR"/*.log |
| |
| $CHRONYD_WRAPPER "$chronyd" $(get_chronyd_options) > "$TEST_DIR/chronyd.out" 2>&1 |
| |
| [ $? -eq 0 ] && [ -f "$pidfile" ] && ps -p "$(cat "$pidfile")" > /dev/null && test_ok || test_error |
| } |
| |
| wait_for_sync() { |
| local prev_length |
| |
| test_message 1 0 "waiting for synchronization" |
| prev_length=$msg_length |
| |
| for i in $(seq 1 10); do |
| run_chronyc "ntpdata $server" > /dev/null 2>&1 || break |
| if check_chronyc_output "Total RX +: [1-9]" > /dev/null 2>&1; then |
| msg_length=$prev_length |
| test_ok |
| return |
| fi |
| sleep 1 |
| done |
| |
| msg_length=$prev_length |
| 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" 2> /dev/null; 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 1110 " "$TEST_LOGDIR/measurements.log" && \ |
| [ -f "$TEST_LOGDIR/tempcomp.log" ] && [ "$(wc -l < "$TEST_LOGDIR/tempcomp.log")" -ge 2 ] && \ |
| test_ok || test_bad |
| } |
| |
| # Run a chronyc command |
| run_chronyc() { |
| local host=$chronyc_host options="-n -m" |
| |
| test_message 1 0 "running chronyc $([ -n "$host" ] && echo "@$host ")$*" |
| |
| if [ -z "$host" ]; then |
| host="$(get_cmdsocket)" |
| else |
| options="$options -p $(grep cmdport "$(get_conffile)" | awk '{print $2}')" |
| fi |
| |
| $CHRONYC_WRAPPER "$chronyc" -h "$host" $options "$@" > "$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 |
| } |