#!/bin/bash # Simple test wrapper around radclient to allow automated UATs # # Author Arran Cudbard-Bell <a.cudbardb@freeradius.org> # Copyright 2014-2015 Arran Cudbard-Bell <a.cudbardb@freeradius.org> # Copyright 2015 The FreeRADIUS Project # A POSIX variable OPTIND=1 # Reset in case getopts has been used previously in the shell. # Environmental variables : ${TESTDIR=$(dirname $0)"/tests"} : ${RADCLIENT='radclient'} : ${FILTER_SUFFIX='_expected'} : ${DICTPATH=$(dirname $0)/share} PATH="$(dirname $0)/bin:${PATH}" # Initialize our own variables verbose=0 cluster= role= type= parallel=40 retries=3 timeout=2 target='127.0.0.1' secret='testing123' # Some very basic logging functions function ERROR { echo "$@" 1>&2; } function INFO { echo "$@" } function DEBUG { if [ $verbose -gt 0 ]; then echo "$@" fi } function show_help { echo $(basename $0)" [options] [-- <test_glob0> <test_glob1> <test_globN>]" echo " -h Display this help message." echo " -H <host>[:port] Send test packets to specified host and port (defaults to 127.0.0.1)" echo " -v Verbose mode." echo " -p <number> Run tests in parallel (defaults to 20)." echo " -s <secret> Shared secret." if [ ! -z "$role_types" ]; then echo " -c <cluster> Specify cluster type one of ($cluster_types)." echo " -r <type> Specify server role one of ($role_types)." echo echo "Note: Test path globs are relative to ${TESTDIR}/<cluster>/<type>/" fi echo echo "For role based test file layout create test files under ${TESTDIR}/<cluster>/<type>" echo "Where <cluster> and <type> are substrings found in the FQDN of <host>." echo "For simplified test layout create test files under ${TESTDIR}" echo echo "The directory containing the tests should contains pairs of request files and filter files." echo "The request file name must contain 'test<num><num><num>." echo "The filter name must match the test name but with a '${FILTER_SUFFIX}' suffix." echo "For example:" echo " ${TESTDIR}/test000_my_first_test" echo " ${TESTDIR}/test000_my_first_test${FILTER_SUFFIX}" echo echo "The directory containing the tests may have multiple subdirectories to group the tests." } RADCLIENT=$(command -v "$RADCLIENT") if [ ! -x "$RADCLIENT" ]; then ERROR "Can't find radclient binary, modify your \$PATH or set RADCLIENT" exit 64 fi if [ ! -d "$TESTDIR" ]; then ERROR "Test dir $TESTDIR does not exist, create it or specify it with TESTDIR=<dir>" show_help exit 64 fi # Definitions (build these dynamically by looking at the files under tests) cluster_dirs=$(find "$TESTDIR/" -mindepth 1 -maxdepth 1 -type d) cluster_types=$(echo $cluster_dirs | sed 's/\s/ /g') role_types= for i in $cluster_dirs; do for j in $(find "$TESTDIR/$(basename $i)/" -mindepth 1 -maxdepth 1 -type d); do role=$(basename "$j") if [ "$role_types" == '' ]; then role_types="$role" else role_types+="\n$role" fi done done if [ -z "$role_types" ]; then DEBUG "Using simple test file layout" else DEBUG "Using role based test file layout" role_types=$(echo -e "$role_types" | sort | uniq) # Remove duplicates role_types=$(echo $role_types | sed 's/\s/ /g') # Change \n back to spaces fi while getopts "h?H:vc:r:s:p:" opt; do case "$opt" in h|\?) show_help exit 0 ;; v) verbose=1 ;; c) found=0 for i in $cluster_types; do if [ "$i" == "$OPTARG" ]; then found=1 fi done if [ $found -ne 1 ]; then ERROR "'$OPTARG' is not a valid cluster type" show_help exit 64 fi cluster="$OPTARG" ;; r) found=0 for i in $role_types; do if [ "$i" == "$OPTARG" ]; then found=1 fi done if [ $found -ne 1 ]; then ERROR "'$OPTARG' is not a valid role type" show_help exit 64 fi role="$OPTARG" ;; s) secret="$OPTARG" ;; p) if ! echo "$OPTARG" | grep -E '^[0-9]+$' > /dev/null; then ERROR "Non integer argument '$OPTARG' specified for -p" show_help exit 64 fi parallel=$OPTARG ;; H) target="$OPTARG" ;; esac done shift $((OPTIND-1)) [ "$1" = "--" ] && shift test_files=$@ # # Match keywords from the hostname to clusters or roles # if [ ! -z "$role_types" ]; then this_host=$(hostname -f) for tok in $(echo "$this_host" | sed 's/\./ /g'); do for key in ${cluster_types}; do if echo "$tok" | grep "$key" > /dev/null && [ "$cluster" = '' ]; then cluster="$key"; fi done for key in ${role_types}; do if echo "$tok" | grep "$key" > /dev/null && [ "$role" = '' ]; then role="$key"; fi done done if [ "$cluster" == '' ]; then ERROR "Couldn't determine the cluster $this_host belongs to"; show_help exit 64; fi if [ "$role" == '' ]; then ERROR "Couldn't determine the role $this_host performs"; show_help exit 64; fi test_path="${TESTDIR}/${cluster}/${role}" # # Otherwise just use the tests in the test dir # else test_path="${TESTDIR}" fi if [ "$test_files" != '' ]; then tmp= for glob in $test_files; do # Filter out response files (makes wildcards easier), and expand the globs for file in $(find "${test_path}" -depth -path "*${glob}" -and -not -path "*${FILTER_SUFFIX}" -and '(' -type f -or -type l ')'); do tmp+="${file} " done done test_files="${tmp}" else # Lexicographical, depth-first test_files=$(find "$test_path" -depth -path '*test[0-9][0-9][0-9]*' -and -not -path "*${FILTER_SUFFIX}" -and '(' -type f -or -type l ')') if [ "$test_files" == '' ]; then ERROR "No test files found in $test_path" exit 64; fi INFO "Executing"$(echo "$test_files" | wc -l)" test(s) from ${test_path}" fi # # Check if we got any test files # if [ "$test_files" == '' ]; then ERROR "No test files to process" exit 1 fi # # Output which files were going to be using for testing # if [ $verbose -eq 0 ]; then INFO "Executing specified tests" INFO "Use -v to see full list" else INFO "Executing specified tests:" for i in $test_files; do DEBUG "$i" done fi # # Figure out which tests we can munge into a single file which we can # use to parallelise testing # base=$(basename $0) packets=$(mktemp -t "${base}XXX") || exit 1 filters=$(mktemp -t "${base}XXX") || exit 1 args= file_args= serial_file_args= for i in $test_files; do if [ ! -f "$i" -a ! -L "$i" ]; then INFO "Skipping $i: not file" continue fi if [ ! -r "$i" ]; then INFO "Skipping $i: not readable (check permissions)" continue fi expected="${i}${FILTER_SUFFIX}" if [ ! -f "$expected" -a ! -L "$expected" ]; then DEBUG "$i cannot be parallelised: Can't find 'expected' file" file_args+=" -f \"$i\"" continue fi if [ ! -r "$expected" ]; then INFO "$i cannot be parallelised: 'expected' file not readable" file_args+=" -f \"${i}:${expected}\"" continue fi if head -n 1 "$i" | grep -i -E '^#\s*serial' > /dev/null; then DEBUG "$i marked as serial only" serial_file_args+=" -f \"${i}:${expected}\"" continue fi # Else add it to the master test file printf '%s\n' "$(cat "$i")" >> "$packets" # Add the name of the file so it appears in radclient debug output # and can later be specified with -v -- <test> to drill down. echo "Radclient-Test-Name := \""$(echo "$i" | sed -e "s@${test_path}/\?@@")"\"" >> "$packets" echo >> "$packets" printf '%s\n' "$(cat "${i}_expected")" >> "$filters" echo >> "$filters" done if [ `cat "$packets" | wc -l` -gt 0 ]; then file_args+=" -f \"${packets}:${filters}\"" fi if [ ! -z "$file_args" ]; then args="$file_args" if [ $verbose -ne 0 ]; then args+=" -x" fi args+=" -s" args+=" -t \"$timeout\"" args+=" -r \"$retries\"" args+=" -p \"$parallel\"" args+=" -D \"$DICTPATH\"" args+=" \"$target\"" args+=" auto" args+=" \"$secret\"" DEBUG "Executing: $RADCLIENT $args" eval $RADCLIENT $args; ret=$? INFO "(Parallelised tests)" INFO "" rm -f "$packets" 2>&1 > /dev/null rm -f "$filters" 2>&1 > /dev/null if [ $ret -ne 0 ]; then ERROR "One or more tests failed (radclient exited with $ret)" exit $ret fi fi if [ ! -z "$serial_file_args" ]; then args="$serial_file_args" if [ $verbose -ne 0 ]; then args+=" -x" fi args+=" -s" args+=" -t \"$timeout\"" args+=" -r \"$retries\"" args+=" -p 1" args+=" -D \"$DICTPATH\"" args+=" \"$target\"" args+=" auto" args+=" \"$secret\"" DEBUG "Executing: $RADCLIENT $args" eval $RADCLIENT $args; ret=$? INFO "(Serialised tests)" if [ $ret -ne 0 ]; then ERROR "One or more tests failed (radclient exited with $ret)" exit $ret fi fi exit 0