From 115e83f3aac4198605f6779b15695022c67193e1 Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Thu, 27 Feb 2014 18:07:45 +0100 Subject: [PATCH] Add simulation tests Use clknetsim to run multiple chronyd instances with simulated clocks and network. It allows fast and reproducible testing, without real network. Included are several tests of performance in different clock/network conditions, chronyd options, NTP authentication, chronyc, and past bug fixes. --- Makefile.in | 3 + test/simulation/001-defaults | 13 + test/simulation/002-largenetwork | 22 ++ test/simulation/003-largefreqoffset | 18 ++ test/simulation/004-largetimeoffset | 17 ++ test/simulation/005-externalstep | 18 ++ test/simulation/006-largejitter | 21 ++ test/simulation/007-largewander | 20 ++ test/simulation/101-poll | 28 ++ test/simulation/102-iburst | 23 ++ test/simulation/103-initstepslew | 32 +++ test/simulation/104-driftfile | 22 ++ test/simulation/105-ntpauth | 33 +++ test/simulation/106-refclock | 18 ++ test/simulation/107-allowdeny | 46 +++ test/simulation/108-peer | 33 +++ test/simulation/109-makestep | 30 ++ test/simulation/110-chronyc | 37 +++ test/simulation/201-freqaccumulation | 35 +++ test/simulation/README | 12 + test/simulation/test.common | 413 +++++++++++++++++++++++++++ 21 files changed, 894 insertions(+) create mode 100755 test/simulation/001-defaults create mode 100755 test/simulation/002-largenetwork create mode 100755 test/simulation/003-largefreqoffset create mode 100755 test/simulation/004-largetimeoffset create mode 100755 test/simulation/005-externalstep create mode 100755 test/simulation/006-largejitter create mode 100755 test/simulation/007-largewander create mode 100755 test/simulation/101-poll create mode 100755 test/simulation/102-iburst create mode 100755 test/simulation/103-initstepslew create mode 100755 test/simulation/104-driftfile create mode 100755 test/simulation/105-ntpauth create mode 100755 test/simulation/106-refclock create mode 100755 test/simulation/107-allowdeny create mode 100755 test/simulation/108-peer create mode 100755 test/simulation/109-makestep create mode 100755 test/simulation/110-chronyc create mode 100755 test/simulation/201-freqaccumulation create mode 100644 test/simulation/README create mode 100644 test/simulation/test.common diff --git a/Makefile.in b/Makefile.in index 58258af..9626325 100644 --- a/Makefile.in +++ b/Makefile.in @@ -129,6 +129,9 @@ install: chronyd chronyc chrony.txt %.s : %.c $(CC) $(CFLAGS) $(CPPFLAGS) -S $< +check : + cd test/simulation; ./run + install-docs : docs [ -d $(DESTDIR)$(DOCDIR) ] || mkdir -p $(DESTDIR)$(DOCDIR) cp chrony.txt $(DESTDIR)$(DOCDIR)/chrony.txt diff --git a/test/simulation/001-defaults b/test/simulation/001-defaults new file mode 100755 index 0000000..466d43b --- /dev/null +++ b/test/simulation/001-defaults @@ -0,0 +1,13 @@ +#!/bin/bash + +. test.common + +test_start "default test settings" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/002-largenetwork b/test/simulation/002-largenetwork new file mode 100755 index 0000000..62885e5 --- /dev/null +++ b/test/simulation/002-largenetwork @@ -0,0 +1,22 @@ +#!/bin/bash + +. test.common + +test_start "large network" + +time_rms_limit=5e-4 + +server_strata=3 +servers=4 +clients=5 + +client_start=2000 +min_sync_time=2100 +max_sync_time=2300 + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/003-largefreqoffset b/test/simulation/003-largefreqoffset new file mode 100755 index 0000000..e463f0e --- /dev/null +++ b/test/simulation/003-largefreqoffset @@ -0,0 +1,18 @@ +#!/bin/bash + +. test.common + +test_start "large frequency offset" + +max_sync_time=1000 + +for freq_offset in -5e-2 -5e-3 5e-3 5e-2; do + # Adjust offset so it's close to 0 on first clock update + time_offset=$(awk "BEGIN {print -($freq_offset * 130)}") + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/004-largetimeoffset b/test/simulation/004-largetimeoffset new file mode 100755 index 0000000..273056c --- /dev/null +++ b/test/simulation/004-largetimeoffset @@ -0,0 +1,17 @@ +#!/bin/bash + +. test.common + +test_start "large time offset" + +min_sync_time=1300 +max_sync_time=1400 + +for time_offset in -1e2 1e2; do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/005-externalstep b/test/simulation/005-externalstep new file mode 100755 index 0000000..3f26324 --- /dev/null +++ b/test/simulation/005-externalstep @@ -0,0 +1,18 @@ +#!/bin/bash + +. test.common + +test_start "external time step" + +min_sync_time=1300 +max_sync_time=1500 + +for step in -1e2 1e2; do + # Make one step in 150th second + client_step="(* $step (equal 0.1 (sum 1.0) 150))" + run_test || test_fail + check_chronyd_exit || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/006-largejitter b/test/simulation/006-largejitter new file mode 100755 index 0000000..50d5db9 --- /dev/null +++ b/test/simulation/006-largejitter @@ -0,0 +1,21 @@ +#!/bin/bash + +. test.common + +test_start "large jitter" + +time_offset=1e0 +jitter=1e-1 + +time_max_limit=5e-1 +freq_max_limit=2e-1 +time_rms_limit=1e-1 +freq_rms_limit=5e-3 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/007-largewander b/test/simulation/007-largewander new file mode 100755 index 0000000..22ffece --- /dev/null +++ b/test/simulation/007-largewander @@ -0,0 +1,20 @@ +#!/bin/bash + +. test.common + +test_start "large wander" + +wander=1e-7 + +time_max_limit=5e-3 +freq_max_limit=5e-3 +time_rms_limit=1e-3 +freq_rms_limit=1e-4 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/101-poll b/test/simulation/101-poll new file mode 100755 index 0000000..ce9bd76 --- /dev/null +++ b/test/simulation/101-poll @@ -0,0 +1,28 @@ +#!/bin/bash + +. test.common +test_start "minpoll/maxpoll options" + +wander=0.0 +jitter=1e-6 + +time_max_limit=1e-5 +freq_max_limit=1e-5 +time_rms_limit=5e-6 +freq_rms_limit=5e-6 +client_conf="makestep 1e-2 1" + +for poll in $(seq 2 14); do + client_server_options="minpoll $poll maxpoll $poll" + limit=$[2**$poll * 10] + min_sync_time=$[2**$poll * 2] + max_sync_time=$[2**$poll * 21 / 10 + 1] + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/102-iburst b/test/simulation/102-iburst new file mode 100755 index 0000000..dd66339 --- /dev/null +++ b/test/simulation/102-iburst @@ -0,0 +1,23 @@ +#!/bin/bash + +. test.common +test_start "iburst option" + +freq_offset=1e-4 + +client_conf="makestep 1e-2 1 +driftfile tmp/drift" +client_server_options="iburst" + +min_sync_time=4 +max_sync_time=6 + +echo "100 1.0" > tmp/drift + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/103-initstepslew b/test/simulation/103-initstepslew new file mode 100755 index 0000000..89611f7 --- /dev/null +++ b/test/simulation/103-initstepslew @@ -0,0 +1,32 @@ +#!/bin/bash + +. test.common +test_start "initstepslew directive" + +freq_offset=0.0 +wander=0.0 +limit=100 + +# clknetsim requires source port (if bound) to match dest port +client_server_options="port 124" +client_conf="initstepslew 5 192.168.123.1 +port 124" + +min_sync_time=15 +max_sync_time=30 + +for time_offset in -2.0 -0.2 0.2 2.0; do + run_test || test_fail + check_chronyd_exit || test_fail + check_sync || test_fail +done + +min_sync_time=1 +max_sync_time=1 + +for time_offset in -1e8 -1e2 1e2 1e8; do + run_test || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/104-driftfile b/test/simulation/104-driftfile new file mode 100755 index 0000000..b65452d --- /dev/null +++ b/test/simulation/104-driftfile @@ -0,0 +1,22 @@ +#!/bin/bash + +. test.common +test_start "driftfile directive" + +servers=0 +time_offset=0.0 +wander=0.0 +limit=10 +freq_max_limit=1e-9 +min_sync_time=1 +max_sync_time=1 +client_conf="driftfile tmp/drift" + +for freq_offset in -5e-2 -5e-4 -5e-6 5e-6 5e-4 5e-2; do + awk "BEGIN {printf \"%.9e 1\", 1e6 - 1 / (1 + $freq_offset) * 1e6}" > tmp/drift + run_test || test_fail + check_chronyd_exit || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/105-ntpauth b/test/simulation/105-ntpauth new file mode 100755 index 0000000..3e88d92 --- /dev/null +++ b/test/simulation/105-ntpauth @@ -0,0 +1,33 @@ +#!/bin/bash + +. test.common + +test_start "NTP authentication" + +server_conf="keyfile tmp/keys" +client_conf="keyfile tmp/keys" + +cat > tmp/keys <<-EOF +1 $(tr -c -d 'a-zA-Z0-9' < /dev/urandom 2> /dev/null | head -c 24) +2 ASCII:$(tr -c -d 'a-zA-Z0-9' < /dev/urandom 2> /dev/null | head -c 24) +3 MD5 ASCII:$(tr -c -d 'a-zA-Z' < /dev/urandom 2> /dev/null | head -c 24) +4 MD5 HEX:$(tr -c -d '0-9A-F' < /dev/urandom 2> /dev/null | head -c 32) +EOF + +for key in 1 2 3 4; do + client_server_options="key $key" + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +server_conf="" + +run_test || test_fail +check_chronyd_exit || test_fail +# This check must fail as server doesn't know the key +check_sync && test_fail + +test_pass diff --git a/test/simulation/106-refclock b/test/simulation/106-refclock new file mode 100755 index 0000000..ed04406 --- /dev/null +++ b/test/simulation/106-refclock @@ -0,0 +1,18 @@ +#!/bin/bash + +. test.common +test_start "SHM refclock" + +servers=0 +limit=1000 +refclock_jitter=$jitter +min_sync_time=60 +max_sync_time=80 +client_conf="refclock SHM 0" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/107-allowdeny b/test/simulation/107-allowdeny new file mode 100755 index 0000000..7b16312 --- /dev/null +++ b/test/simulation/107-allowdeny @@ -0,0 +1,46 @@ +#!/bin/bash + +. test.common + +test_start "allow/deny directives" + +limit=500 + +# Note that start_client in clknetsim.bash always adds allow to the config + +for server_conf in \ + "deny" \ + "deny all" \ + "deny 192.168.0.0/16" \ + "deny 192.168.123" \ + "deny 192.168.123.2" \ + "deny all +allow 192.168.124.0/24" +do + run_test || test_fail + check_chronyd_exit || test_fail + # These checks are expected to fail + check_source_selection && test_fail + check_packet_interval && test_fail + check_sync && test_fail +done + +for server_conf in \ + "deny all +allow" \ + "deny all +allow all" \ + "deny all +allow 192.168.123" \ + "deny all +allow 192.168.123/24" \ + "deny 192.168.124.0/24" +do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/108-peer b/test/simulation/108-peer new file mode 100755 index 0000000..b4d21d2 --- /dev/null +++ b/test/simulation/108-peer @@ -0,0 +1,33 @@ +#!/bin/bash + +. test.common + +test_start "NTP peers" + +# Allow and drop packets to the server in 1000 second intervals, so only one +# client has access to it and the other is forced to switch to the peer. +base_delay=$(cat <<-EOF | tr -d '\n' + (+ 1e-4 + (* -1 + (equal 0.1 from 2) + (equal 0.1 to 1) + (equal 0.1 (min (% time 2000) 1000) 1000)) + (* -1 + (equal 0.1 from 3) + (equal 0.1 to 1) + (equal 0.1 (max (% time 2000) 1000) 1000))) +EOF +) + +clients=2 +peers=2 +max_sync_time=1000 +client_server_options="minpoll 6 maxpoll 6" +client_peer_options="minpoll 6 maxpoll 6" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/109-makestep b/test/simulation/109-makestep new file mode 100755 index 0000000..e74b719 --- /dev/null +++ b/test/simulation/109-makestep @@ -0,0 +1,30 @@ +#!/bin/bash + +. test.common +test_start "makestep directive" + +limit=200 +jitter=1e-5 +client_conf="makestep 2 1" + +min_sync_time=140 +max_sync_time=160 + +for time_offset in -1.0 -0.1 0.1 1.0; do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail +done + +min_sync_time=120 +max_sync_time=140 + +for time_offset in -1e8 -1e2 1e2 1e8; do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/110-chronyc b/test/simulation/110-chronyc new file mode 100755 index 0000000..535a126 --- /dev/null +++ b/test/simulation/110-chronyc @@ -0,0 +1,37 @@ +#!/bin/bash + +. test.common + +test_start "chronyc" + +chronyc_conf="tracking +sources +sourcestats" + +run_test || test_fail +check_chronyd_exit || test_fail + +check_chronyc_output "^Reference ID : 192\.168\.123\.1 \(192\.168\.123\.1\) +Stratum : 2 +Ref time \(UTC\) : Fri Jan 1 00:1.:.. 2010 +System time : 0\.0000..... seconds (slow|fast) of NTP time +Last offset : -?0\.0000..... seconds +RMS offset : 0\.000...... seconds +Frequency : (99|100)\.... ppm fast +Residual freq : -?0.00. ppm +Skew : 0\.... ppm +Root delay : 0\.000... seconds +Root dispersion : 0.000... seconds +Update interval : 6.\.. seconds +Leap status : Normal +210 Number of sources = 1 +MS Name/IP address Stratum Poll Reach LastRx Last sample +=============================================================================== +\^\* 192\.168\.123\.1 1 [67] 377 [0-9]+ [0-9 +-]+[un]s\[[0-9 +-]+[un]s\] \+/-[ 0-9]+[un]s +210 Number of sources = 1 +Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev +============================================================================== +192\.168\.123\.1 [0-9 ]+ [0-9 ]+ [0-9 ]+ [ +-]0\.0.. 0\.... [0-9 +-]+[un]s [0-9 ]+[un]s$" \ +|| test_fail + +test_pass diff --git a/test/simulation/201-freqaccumulation b/test/simulation/201-freqaccumulation new file mode 100755 index 0000000..a8ba917 --- /dev/null +++ b/test/simulation/201-freqaccumulation @@ -0,0 +1,35 @@ +#!/bin/bash + +. test.common + +# Test fix in commit 60d0fa299307076143da94d36deb7b908fa9bdb7 + +test_start "frequency accumulation" + +time_offset=100.0 +jitter=1e-6 +base_delay=1e-6 +wander=0.0 + +limit=200 +time_max_limit=1e-5 +freq_max_limit=1e-7 +time_rms_limit=1e-5 +freq_rms_limit=1e-7 +min_sync_time=120 +max_sync_time=140 + +client_server_options="minpoll 6 maxpoll 6" +client_conf="driftfile tmp/drift +makestep 1 1" + +for freq_offset in -5e-2 -5e-4 5e-4 5e-2; do + for drift in -1e+4 -1e+2 1e+2 1e+4; do + echo "$drift 100000" > tmp/drift + run_test || test_fail + check_chronyd_exit || test_fail + check_sync || test_fail + done +done + +test_pass diff --git a/test/simulation/README b/test/simulation/README new file mode 100644 index 0000000..c875ad6 --- /dev/null +++ b/test/simulation/README @@ -0,0 +1,12 @@ +This is a collection of simulation tests. They use clknetsim to simulate +multiple systems connected in a network. It's available at + +https://github.com/mlichvar/clknetsim + +If this directory doesn't have a clknetsim subdirectory, a known working +revision will be downloaded and compiled automatically. + +Currently it runs only on Linux. + +The tests are written in bash and they can be run directly. The ./run script +runs all tests. diff --git a/test/simulation/test.common b/test/simulation/test.common new file mode 100644 index 0000000..6635128 --- /dev/null +++ b/test/simulation/test.common @@ -0,0 +1,413 @@ +# Copyright (C) 2013-2014 Miroslav Lichvar +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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, see . + +export LC_ALL=C +export PATH=../../:$PATH +export CLKNETSIM_PATH=clknetsim + +# Known working clknetsim revision +clknetsim_revision=eb0b85335c40027ac941b2276142542fff482107 +clknetsim_url=https://github.com/mlichvar/clknetsim/archive/$clknetsim_revision.tar.gz + +# Only Linux is supported +if [ "$(uname -s)" != Linux ]; then + echo "Simulation tests supported only on Linux" + exit 3 +fi + +# Try to download clknetsim if not found +if [ ! -e $CLKNETSIM_PATH ]; then + curl -L "$clknetsim_url" | tar xz || exit 3 + ln -s clknetsim-$clknetsim_revision clknetsim || exit 3 +fi + +# Try to build clknetsim if not built +if [ ! -x $CLKNETSIM_PATH/clknetsim -o ! -e $CLKNETSIM_PATH/clknetsim.so ]; then + make -C clknetsim || exit 3 +fi + +. $CLKNETSIM_PATH/clknetsim.bash + +# Default test testings + +default_limit=10000 +default_time_offset=1e-1 +default_freq_offset=1e-4 +default_base_delay=1e-4 +default_jitter=1e-4 +default_wander=1e-9 +default_refclock_jitter="" + +default_update_interval=0 +default_log_packets=0 +default_shift_pll=2 + +default_server_strata=1 +default_servers=1 +default_clients=1 +default_peers=0 +default_server_start=0.0 +default_client_start=0.0 +default_chronyc_start=1000.0 +default_server_step="" +default_client_step="" + +default_server_server_options="" +default_client_server_options="" +default_server_peer_options="" +default_client_peer_options="" +default_server_conf="" +default_client_conf="" +default_chronyc_conf="" +default_chronyd_options="" + +default_time_max_limit=1e-3 +default_freq_max_limit=5e-4 +default_time_rms_limit=2e-4 +default_freq_rms_limit=1e-5 +default_min_sync_time=120 +default_max_sync_time=210 + +# Initialize test settings from their defaults +for defopt in $(declare | grep '^default_'); do + defoptname=${defopt%%=*} + optname=${defoptname#default_} + eval "[ -z \"\${$optname:+a}\" ] && $optname=\"\$$defoptname\"" +done + +test_start() { + rm -f tmp/* + echo "Testing $@:" +} + +test_pass() { + echo "PASS" + exit 0 +} + +test_fail() { + echo "FAIL" + exit 1 +} + +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 +} + +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 +} + +get_wander_expr() { + local scaled_wander + + scaled_wander=$(awk "BEGIN {print $wander / \ + sqrt($update_interval < 0 ? 2^-($update_interval) : 1)}") + + echo "(+ $freq_offset (sum (* $scaled_wander (normal))))" +} + + +get_delay_expr() { + echo "(+ $base_delay (* $jitter (exponential)))" +} + +get_refclock_expr() { + echo "(* $refclock_jitter (normal))" +} + +get_chronyd_nodes() { + echo $[$servers * $server_strata + $clients] +} + +get_chronyd_conf() { + local i stratum=$1 peer=$2 + + if [ $stratum -eq 1 ]; then + echo "local stratum 1" + echo "$server_conf" + elif [ $stratum -le $server_strata ]; then + for i in $(seq 1 $servers); do + echo "server 192.168.123.$[$servers * ($stratum - 2) + $i] $server_server_options" + done + for i in $(seq 1 $peers); do + [ $i -eq $peer -o $i -gt $servers ] && continue + echo "peer 192.168.123.$[$servers * ($stratum - 1) + $i] $server_peer_options" + done + echo "$server_conf" + else + for i in $(seq 1 $servers); do + echo "server 192.168.123.$[$servers * ($stratum - 2) + $i] $client_server_options" + done + for i in $(seq 1 $peers); do + [ $i -eq $peer -o $i -gt $clients ] && continue + echo "peer 192.168.123.$[$servers * ($stratum - 1) + $i] $client_peer_options" + done + echo "$client_conf" + fi +} + +# Check if the clock was well synchronized +check_sync() { + local i sync_time max_time_error max_freq_error ret=0 + local rms_time_error rms_freq_error + + test_message 2 1 "checking clock sync time, max/rms time/freq error:" + + for i in $(seq 1 $(get_chronyd_nodes)); do + [ $i -gt $[$servers * $server_strata] ] || continue + + sync_time=$(find_sync tmp/log.offset tmp/log.freq $i \ + $time_max_limit $freq_max_limit 1.0) + max_time_error=$(get_stat 'Maximum absolute offset' $i) + max_freq_error=$(get_stat 'Maximum absolute frequency' $i) + rms_time_error=$(get_stat 'RMS offset' $i) + rms_freq_error=$(get_stat 'RMS frequency' $i) + + test_message 3 0 "node $i: $sync_time $(printf '%.2e %.2e %.2e %.2e' \ + $max_time_error $max_freq_error $rms_time_error $rms_freq_error)" + + check_stat $sync_time $min_sync_time $max_sync_time && \ + check_stat $max_time_error 0.0 $time_max_limit && \ + check_stat $max_freq_error 0.0 $freq_max_limit && \ + check_stat $rms_time_error 0.0 $time_rms_limit && \ + check_stat $rms_freq_error 0.0 $freq_rms_limit && \ + test_ok || test_bad + + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Check if chronyd exited properly +check_chronyd_exit() { + local i ret=0 + + test_message 2 1 "checking chronyd exit:" + + for i in $(seq 1 $(get_chronyd_nodes)); do + test_message 3 0 "node $i:" + + tail -n 1 tmp/log.$i | grep -q 'chronyd exiting' && \ + test_ok || test_bad + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Check for problems in source selection +check_source_selection() { + local i ret=0 + + test_message 2 1 "checking source selection:" + + for i in $(seq $[$servers * $server_strata + 1] $(get_chronyd_nodes)); do + test_message 3 0 "node $i:" + + ! grep -q 'no majority\|no reachable sources' tmp/log.$i && \ + grep -q 'Selected source' tmp/log.$i && \ + test_ok || test_bad + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Check if incoming and outgoing packet intervals are sane +check_packet_interval() { + local i ret=0 in_interval out_interval + + test_message 2 1 "checking incoming and outgoing packet interval:" + + for i in $(seq 1 $(get_chronyd_nodes)); do + in_interval=$(get_stat 'Mean incoming packet interval' $i) + out_interval=$(get_stat 'Mean outgoing packet interval' $i) + + test_message 3 0 "node $i: $(printf '%.2e %.2e' \ + $in_interval $out_interval)" + + # Check that the intervals are non-zero and shorter than limit, + # incoming is not longer than outgoing for stratum 1 servers, and + # outgoing is not longer than incoming for clients. + nodes=$[$servers * $server_strata + $clients] + check_stat $in_interval 0.1 $limit && \ + check_stat $out_interval 0.1 $limit && \ + ([ $i -gt $servers ] || \ + check_stat $in_interval 0.0 $out_interval) && \ + ([ $i -le $[$servers * $server_strata] ] || \ + check_stat $in_interval 0.0 $out_interval) && \ + test_ok || test_bad + + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Compare chronyc output with specified pattern +check_chronyc_output() { + local i ret=0 pattern=$1 + + test_message 2 1 "checking chronyc output:" + + for i in $(seq $[$(get_chronyd_nodes) + 1] $[$(get_chronyd_nodes) + $clients]); do + test_message 3 0 "node $i:" + + [[ "$(cat tmp/log.$i)" =~ $pattern ]] && \ + test_ok || test_bad + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Print test settings which differ from default value +print_nondefaults() { + local defopt defoptname optname + + test_message 2 1 "non-default settings:" + declare | grep '^default_*' | while read defopt; do + defoptname=${defopt%%=*} + optname=${defoptname#default_} + eval "[ \"\$$optname\" = \"\$$defoptname\" ]" || \ + test_message 3 1 $(eval "echo $optname=\$$optname") + done +} + +run_simulation() { + local nodes=$1 + + test_message 2 0 "running simulation:" + + start_server $nodes \ + -o tmp/log.offset -f tmp/log.freq \ + $([ $log_packets -ne 0 ] && echo -p tmp/log.packets) \ + -R $(awk "BEGIN {print $update_interval < 0 ? 2^-($update_interval) : 1}") \ + -r $(awk "BEGIN {print $max_sync_time * 2^$update_interval}") \ + -l $(awk "BEGIN {print $limit * 2^$update_interval}") && test_ok || test_error +} + +run_test() { + local i j n stratum node nodes step start freq offset conf + + test_message 1 1 "network with $servers*$server_strata servers and $clients clients:" + print_nondefaults + + nodes=$(get_chronyd_nodes) + [ -n "$chronyc_conf" ] && nodes=$[$nodes + $clients] + + for i in $(seq 1 $nodes); do + echo "node${i}_shift_pll = $shift_pll" + for j in $(seq 1 $nodes); do + [ $i -eq $j ] && continue + echo "node${i}_delay${j} = $(get_delay_expr)" + echo "node${j}_delay${i} = $(get_delay_expr)" + done + done > tmp/conf + + node=1 + + for stratum in $(seq 1 $[$server_strata + 1]); do + [ $stratum -le $server_strata ] && n=$servers || n=$clients + + for i in $(seq 1 $n); do + test_message 2 0 "starting node $node:" + if [ $stratum -eq 1 ]; then + step=$server_step + start=$server_start + freq="" + offset=0.0 + elif [ $stratum -le $server_strata ]; then + step=$server_step + start=$server_start + freq=$(get_wander_expr) + offset=0.0 + else + step=$client_step + start=$client_start + freq=$(get_wander_expr) + offset=$time_offset + fi + + conf=$(get_chronyd_conf $stratum $i $n) + + [ -z "$freq" ] || echo "node${node}_freq = $freq" >> tmp/conf + [ -z "$step" ] || echo "node${node}_step = $step" >> tmp/conf + [ -z "$refclock_jitter" ] || \ + echo "node${node}_refclock = $(get_refclock_expr)" >> tmp/conf + echo "node${node}_offset = $offset" >> tmp/conf + echo "node${node}_start = $start" >> tmp/conf + start_client $node chronyd "$conf" "" "$chronyd_options" && \ + test_ok || test_error + + [ $? -ne 0 ] && return 1 + node=$[$node + 1] + done + done + + for i in $(seq 1 $[$nodes - $node + 1]); do + test_message 2 0 "starting node $node:" + + echo "node${node}_start = $chronyc_start" >> tmp/conf + start_client $node chronyc "$chronyc_conf" "" \ + "-n -h 192.168.123.$[$node - $clients]" && \ + test_ok || test_error + + [ $? -ne 0 ] && return 1 + node=$[$node + 1] + done + + run_simulation $nodes +}