#!/bin/bash # Simple test wrapper around radclient to allow automated UATs # # Author Arran Cudbard-Bell # Copyright 2014-2015 Arran Cudbard-Bell # 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] [-- ]" echo " -h Display this help message." echo " -H [:port] Send test packets to specified host and port (defaults to 127.0.0.1)" echo " -v Verbose mode." echo " -p Run tests in parallel (defaults to 20)." echo " -s Shared secret." if [ ! -z "$role_types" ]; then echo " -c Specify cluster type one of ($cluster_types)." echo " -r Specify server role one of ($role_types)." echo echo "Note: Test path globs are relative to ${TESTDIR}///" fi echo echo "For role based test file layout create test files under ${TESTDIR}//" echo "Where and are substrings found in the FQDN of ." 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." 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=" 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 -- 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