1#!/bin/sh
2
3# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
4#
5# SPDX-License-Identifier: MPL-2.0
6#
7# This Source Code Form is subject to the terms of the Mozilla Public
8# License, v. 2.0.  If a copy of the MPL was not distributed with this
9# file, you can obtain one at https://mozilla.org/MPL/2.0/.
10#
11# See the COPYRIGHT file distributed with this work for additional
12# information regarding copyright ownership.
13
14#
15# Run a system test.
16#
17
18SYSTEMTESTTOP="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"
19. $SYSTEMTESTTOP/conf.sh
20
21if [ "$(id -u)" -eq "0" ] && ! ${NAMED} -V | grep -q -F -- "enable-developer"; then
22    echofail "Refusing to run test as root. Build with --enable-developer to override." >&2
23    exit 1
24fi
25
26export SYSTEMTESTTOP
27
28date_with_args() (
29    date "+%Y-%m-%dT%T%z"
30)
31
32stopservers=true
33baseport=5300
34
35if [ ${SYSTEMTEST_NO_CLEAN:-0} -eq 1 ]; then
36    clean=false
37else
38    clean=true
39fi
40
41restart=false
42while getopts "knp:r-:t" flag; do
43    case "$flag" in
44    -) case "${OPTARG}" in
45               keep) stopservers=false ;;
46               noclean) clean=false ;;
47           esac
48           ;;
49    k) stopservers=false ;;
50    n) clean=false ;;
51    p) baseport=$OPTARG ;;
52    t) restart=true ;;
53    esac
54done
55shift `expr $OPTIND - 1`
56
57if [ $# -eq 0 ]; then
58    echofail "Usage: $0 [-k] [-n] [-p <PORT>] test-directory [test-options]" >&2;
59    exit 1
60fi
61
62systest=${1%%/}
63shift
64
65if [ ! -d $systest ]; then
66    echofail "$0: $systest: no such test" >&2
67    exit 1
68fi
69
70# Define the number of ports allocated for each test, and the lowest and
71# highest valid values for the "-p" option.
72#
73# The lowest valid value is one more than the highest privileged port number
74# (1024).
75#
76# The highest valid value is calculated by noting that the value passed on the
77# command line is the lowest port number in a block of "numports" consecutive
78# ports and that the highest valid port number is 65,535.
79numport=100
80minvalid=`expr 1024 + 1`
81maxvalid=`expr 65535 - $numport + 1`
82
83test "$baseport" -eq "$baseport" > /dev/null 2>&1
84if [ $? -ne 0 ]; then
85    echofail "$0: $systest: must specify a numeric value for the port" >&2
86    exit 1
87elif [ $baseport -lt $minvalid -o $baseport -gt $maxvalid  ]; then
88    echofail "$0: $systest: the specified port must be in the range $minvalid to $maxvalid" >&2
89    exit 1
90fi
91
92# Name the first 10 ports in the set (it is assumed that each test has access
93# to ten or more ports): the query port, the control port and eight extra
94# ports.  Since the lowest numbered port (specified in the command line)
95# will usually be a multiple of 10, the names are chosen so that if this is
96# true, the last digit of EXTRAPORTn is "n".
97PORT=$baseport
98EXTRAPORT1=`expr $baseport + 1`
99EXTRAPORT2=`expr $baseport + 2`
100EXTRAPORT3=`expr $baseport + 3`
101EXTRAPORT4=`expr $baseport + 4`
102EXTRAPORT5=`expr $baseport + 5`
103EXTRAPORT6=`expr $baseport + 6`
104EXTRAPORT7=`expr $baseport + 7`
105EXTRAPORT8=`expr $baseport + 8`
106CONTROLPORT=`expr $baseport + 9`
107
108LOWPORT=$baseport
109HIGHPORT=`expr $baseport + $numport - 1`
110
111export PORT
112export EXTRAPORT1
113export EXTRAPORT2
114export EXTRAPORT3
115export EXTRAPORT4
116export EXTRAPORT5
117export EXTRAPORT6
118export EXTRAPORT7
119export EXTRAPORT8
120export CONTROLPORT
121
122export LOWPORT
123export HIGHPORT
124
125# Start all servers used by the system test.  Ensure all log files written
126# during a system test (tests.sh + potentially multiple *.py scripts) are
127# retained for each run by calling start.pl with the --restart command-line
128# option for all invocations except the first one.
129start_servers() {
130    echoinfo "I:$systest:starting servers"
131    if $restart || [ "$run" -gt 0 ]; then
132        restart_opt="--restart"
133    fi
134    if ! $PERL start.pl ${restart_opt} --port "$PORT" "$systest"; then
135        echoinfo "I:$systest:starting servers failed"
136        return 1
137    fi
138}
139
140stop_servers() {
141    if $stopservers; then
142        echoinfo "I:$systest:stopping servers"
143        if ! $PERL stop.pl "$systest"; then
144            echoinfo "I:$systest:stopping servers failed"
145            return 1
146        fi
147    fi
148}
149
150echostart "S:$systest:$(date_with_args)"
151echoinfo  "T:$systest:1:A"
152echoinfo  "A:$systest:System test $systest"
153echoinfo  "I:$systest:PORTRANGE:${LOWPORT} - ${HIGHPORT}"
154
155if [ x${PERL:+set} = x ]
156then
157    echowarn "I:$systest:Perl not available.  Skipping test."
158    echowarn "R:$systest:SKIPPED"
159    echoend  "E:$systest:$(date_with_args)"
160    exit 0;
161fi
162
163$PERL testsock.pl -p $PORT  || {
164    echowarn "I:$systest:Network interface aliases not set up.  Skipping test."
165    echowarn "R:$systest:SKIPPED"
166    echoend  "E:$systest:$(date_with_args)"
167    exit 0;
168}
169
170# Check for test-specific prerequisites.
171test ! -f $systest/prereq.sh || ( cd $systest && $SHELL prereq.sh "$@" )
172result=$?
173
174if [ $result -eq 0 ]; then
175    : prereqs ok
176else
177    echowarn "I:$systest:Prerequisites missing, skipping test."
178    echowarn "R:$systest:SKIPPED";
179    echoend "E:$systest:$(date_with_args)"
180    exit 0
181fi
182
183# Check for PKCS#11 support
184if
185    test ! -f $systest/usepkcs11 || $SHELL cleanpkcs11.sh
186then
187    : pkcs11 ok
188else
189    echowarn "I:$systest:Need PKCS#11, skipping test."
190    echowarn "R:$systest:PKCS11ONLY"
191    echoend  "E:$systest:$(date_with_args)"
192    exit 0
193fi
194
195# Clean up files left from any potential previous runs except when
196# started with the --restart option.
197if ! $restart; then
198    if test -f "$systest/clean.sh"; then
199        if ! ( cd "${systest}" && $SHELL clean.sh "$@" ); then
200            echowarn "I:$systest:clean.sh script failed"
201            echofail "R:$systest:FAIL"
202            echoend  "E:$systest:$(date_with_args)"
203            exit 1
204        fi
205    fi
206fi
207
208# Set up any dynamically generated test data
209if test -f $systest/setup.sh
210then
211    if ! ( cd "${systest}" && $SHELL setup.sh "$@" ); then
212        echowarn "I:$systest:setup.sh script failed"
213        echofail "R:$systest:FAIL"
214        echoend  "E:$systest:$(date_with_args)"
215        exit 1
216    fi
217fi
218
219status=0
220run=0
221# Run the tests
222if [ -r "$systest/tests.sh" ]; then
223    if start_servers; then
224        ( cd "$systest" && $SHELL tests.sh "$@" )
225        status=$?
226        run=$((run+1))
227        stop_servers || status=1
228    else
229        status=1
230    fi
231fi
232
233if [ $status -eq 0 ]; then
234    if [ -n "$PYTEST" ]; then
235        for test in $(cd "${systest}" && find . -name "tests*.py"); do
236            rm -f "$systest/$test.status"
237            if start_servers; then
238                run=$((run+1))
239                rm -f "$systest/$test.status"
240                test_status=0
241                (cd "$systest" && "$PYTEST" -v "$test" "$@" || echo "$?" > "$test.status") | SYSTESTDIR="$systest" cat_d
242                if [ -f "$systest/$test.status" ]; then
243                    echo_i "FAILED"
244                    test_status=$(cat "$systest/$test.status")
245                fi
246                status=$((status+test_status))
247                stop_servers || status=1
248            else
249                status=1
250                break
251            fi
252        done
253        rm -f "$systest/$test.status"
254    else
255        echoinfo "I:$systest:pytest not installed, skipping python tests"
256    fi
257fi
258
259if [ "$run" -eq "0" ]; then
260    echoinfo "I:$systest:No tests were found and run"
261    status=255
262fi
263
264
265if $stopservers
266then
267    :
268else
269    exit $status
270fi
271
272get_core_dumps() {
273    find "$systest/" \( -name 'core' -or -name 'core.*' -or -name '*.core' \) ! -name '*.gz' ! -name '*.txt' | sort
274}
275
276core_dumps=$(get_core_dumps | tr '\n' ' ')
277assertion_failures=$(find "$systest/" -name named.run -exec grep "assertion failure" {} + | wc -l)
278sanitizer_summaries=$(find "$systest/" -name 'tsan.*' | wc -l)
279if [ -n "$core_dumps" ]; then
280    echoinfo "I:$systest:Core dump(s) found: $core_dumps"
281    echofail "R:$systest:FAIL"
282    get_core_dumps | while read -r coredump; do
283        SYSTESTDIR="$systest"
284        echoinfo "D:$systest:backtrace from $coredump:"
285        echoinfo "D:$systest:--------------------------------------------------------------------------------"
286        binary=$(gdb --batch --core="$coredump" 2>/dev/null | sed -ne "s|Core was generated by \`\([^' ]*\)[' ].*|\1|p")
287        if [ ! -f "${binary}" ]; then
288            binary=$(find "${TOP}" -path "*/.libs/${binary}" -type f)
289        fi
290        "${TOP}/libtool" --mode=execute gdb \
291                                  -batch \
292                                  -ex bt \
293                                  -core="$coredump" \
294                                  -- \
295                                  "$binary" 2>/dev/null | sed -n '/^Core was generated by/,$p' | cat_d
296        echoinfo "D:$systest:--------------------------------------------------------------------------------"
297        coredump_backtrace="${coredump}-backtrace.txt"
298        echoinfo "D:$systest:full backtrace from $coredump saved in $coredump_backtrace"
299        "${TOP}/libtool" --mode=execute gdb \
300                      -batch \
301                      -command=run.gdb \
302                      -core="$coredump" \
303                      -- \
304                      "$binary" > "$coredump_backtrace" 2>&1
305        echoinfo "D:$systest:core dump $coredump archived as $coredump.gz"
306        gzip -1 "${coredump}"
307    done
308    status=$((status+1))
309elif [ "$assertion_failures" -ne 0 ]; then
310    SYSTESTDIR="$systest"
311    echoinfo "I:$systest:$assertion_failures assertion failure(s) found"
312    find "$systest/" -name 'tsan.*' -exec grep "SUMMARY: " {} + | sort -u | cat_d
313    echofail "R:$systest:FAIL"
314    status=$((status+1))
315elif [ "$sanitizer_summaries" -ne 0 ]; then
316    echoinfo "I:$systest:$sanitizer_summaries sanitizer report(s) found"
317    echofail "R:$systest:FAIL"
318    status=$((status+1))
319elif [ "$status" -ne 0 ]; then
320    echofail "R:$systest:FAIL"
321else
322    echopass "R:$systest:PASS"
323    if $clean && ! $restart; then
324       ( cd $systest && $SHELL clean.sh "$@" )
325       if test -d ../../../.git; then
326           git status -su --ignored "${systest}/" 2>/dev/null | \
327           sed -n -e 's|^?? \(.*\)|I:'${systest}':file \1 not removed|p' \
328           -e 's|^!! \(.*/named.run\)$|I:'${systest}':file \1 not removed|p' \
329           -e 's|^!! \(.*/named.memstats\)$|I:'${systest}':file \1 not removed|p'
330       fi
331    fi
332fi
333
334NAMED_RUN_LINES_THRESHOLD=200000
335find "${systest}" -type f -name "named.run" -exec wc -l {} \; | awk "\$1 > ${NAMED_RUN_LINES_THRESHOLD} { print \$2 }" | sort | while read -r LOG_FILE; do
336    echowarn "I:${systest}:${LOG_FILE} contains more than ${NAMED_RUN_LINES_THRESHOLD} lines, consider tweaking the test to limit disk I/O"
337done
338
339echoend "E:$systest:$(date_with_args)"
340
341exit $status
342