#!/bin/bash
#==============================================================================
# Copyright and license info is available in the LICENSE file included with
# the Server Deployment Package (SDP), and also available online:
# https://swarm.workshop.perforce.com/projects/perforce-software-sdp/view/main/LICENSE
#------------------------------------------------------------------------------
set -u
#==============================================================================
# Declarations and Environment
declare ThisScript="${0##*/}"
declare Version=2.0.0
declare ThisUser=
declare ThisHost=${HOSTNAME%%.*}
declare CmdLine="${0} $*"
declare -i Debug=${SDP_DEBUG:-0}
declare -i ErrorCount=0
declare -i SilentMode=0
declare -i TestCount=0
declare -i TestPassCount=0
declare -i TestFailCount=0
declare -i TestSkipCount=0
declare -i NoOp=0
declare H1="=============================================================================="
declare H2="------------------------------------------------------------------------------"
declare H3="||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
declare Log=
declare LogLink="/tmp/${ThisScript%.sh}.log"
declare P4TestCmd=
declare P4C=
declare P4=p4
declare ExpectedExit=
declare TestLog=
declare -i RequireValidTests=1
declare -i ExpectedExitCode
declare -i ActualExitCode
declare ExpectedString=
declare -i ExpectedStringInOutput=0
declare Comments=
declare CLITestDataOK=1
declare CfgDir=/p4/sdp/test/bsw
declare CLITestDataFile="$CfgDir/cli_tests.cfg"
declare CfgFile=
# Values loaded from general config file.
declare RunHost=
declare TestTag=
declare OutputFile=/tmp/sdp.test_output.$$.$RANDOM
declare -i TestPassed
declare -i Line=0
declare -i TestCount=0
declare -i TestPassCount=0
declare -i TestFailCount=0
GARBAGE+=" $OutputFile"
#==============================================================================
# Local Functions
function msg () { echo -e "$*"; }
function dbg () { [[ "$Debug" -eq 0 ]] || msg "DEBUG: $*"; }
function errmsg () { msg "\\nError: ${1:-Unknown Error}\\n"; ErrorCount+=1; }
function bail () { errmsg "${1:-Unknown Error}"; exit "$ErrorCount"; }
function pass () { TestCount+=1; TestPassCount+=1; msg "PASS Test $TestCount: $*"; }
function fail () { TestCount+=1; TestFailCount+=1; msg "FAIL Test $TestCount: $*"; }
#------------------------------------------------------------------------------
# Function: terminate
function terminate {
local garbageFile=
# Disable signal trapping.
trap - EXIT SIGINT SIGTERM
dbg "$ThisScript: EXITCODE: $ErrorCount"
if [[ -n "$GARBAGE" ]]; then
for garbageFile in $GARBAGE; do
rm -f "$garbageFile"
done
fi
# Stop logging.
[[ "$Log" == off ]] || msg "\\nLog is: $Log\\n${H1}\\n"
# With the trap removed, exit.
exit "$((ErrorCount+TestFailCount+TestSkipCount))"
}
#------------------------------------------------------------------------------
# Function: usage (required function)
#
# Input:
# $1 - style, either -h (for short form) or -man (for man-page like format).
#------------------------------------------------------------------------------
function usage {
declare style=${1:--h}
msg "USAGE for $ThisScript v$Version:
$ThisScript [-d <cfg_dir>] [-f] [-L <log>] [-si] [-v<n>] [-D]
or
$ThisScript [-h|-man|-V]
"
if [[ $style == -man ]]; then
msg "
DESCRIPTION:
This script runs tests for the Server Deployment Package (SDP).
OPTIONS:
-v<n> Set verbosity 1-5 (-v1 = quiet, -v5 = highest).
-f Use '-f' to bypass any invalid test entries in the command line test
data.
-L <log>
Specify the path to a log file, or the special value 'off' to disable
logging. By default, all output (stdout and stderr) goes to:
/tmp/${ThisScript%.sh}.<timestamp>.log
NOTE: This script is self-logging. That is, output displayed on the screen
is simultaneously captured in the log file. It is not necessary (nor harmful)
to use redirection operators like '> log' or '2>&1' or 'tee'.
-si Operate silently. All output (stdout and stderr) is redirected to the log
only; no output appears on the terminal. This cannot be used with
'-L off'.
-D Set extreme debugging verbosity, for debugging the test suite itself.
HELP OPTIONS:
-h Display short help message
-man Display man-style help message
FILES:
The test.<hostname>.cfg files are SDP Test Configuration Files. They
define a set of host-specific values that define how SDP is tested,
where test assets are aquired from, which versions of p4/p4d are used,
etc.
The cli_tests.cfg file defines the command line tests to be executed,
including expected exit codes and output for each.
Note that the /p4/sdp/test directory will not appear on a customer-deployed
SDP in the real world. The /p4/sdp directory will exist, but the 'test'
directory is populated only when using the //sdp/dev_insitu stream.
EXAMPLES:
For typical usage, no arguments are needed. Run this in a Battle School
Lab Environment as perforce@bos-helix-01, after first having run 'lab qa'
as student@bsw-lab-ui to prepare the environment.
\$ cd /p4/sdp/test/bsw
\$ ./$ThisScript
"
fi
exit 1
}
#==============================================================================
# Command Line Processing
declare -i shiftArgs=0
set +u
while [[ $# -gt 0 ]]; do
case $1 in
(-d) CfgDir=$2; shiftArgs=1;;
(-f) RequireValidTests=0;;
(-h) usage -h;;
(-man) usage -man;;
(-L) Log="$2"; shiftArgs=1;;
(-si) SilentMode=1;;
(-n) NoOp=1;;
(-D) set -x;; # Debug; use 'set -x' mode.
(*) usageError "Unknown arg ($1).";;
esac
# Shift (modify $#) the appropriate number of times.
shift; while [[ $shiftArgs -gt 0 ]]; do
[[ $# -eq 0 ]] && usageError "Bad usage."
shiftArgs=$shiftArgs-1
shift
done
done
set -u
#==============================================================================
# Command Line Verification
[[ -n "$Log" ]] || Log="/tmp/${ThisScript%.sh}.$(date +'%Y-%m-%d-%H%M%S').log"
[[ "$SilentMode" -eq 1 && "$Log" == off ]] && \
usageError "Cannot use '-si' with '-L off'."
#==============================================================================
# Main Program
trap terminate EXIT SIGINT SIGTERM
if [[ "$Log" != off ]]; then
touch "$Log" || bail "Couldn't touch log file [$Log]."
# Redirect stdout and stderr to a log file.
if [[ $SilentMode -eq 0 ]]; then
exec > >(tee "$Log")
exec 2>&1
else
exec >"$Log"
exec 2>&1
fi
# Setup /tmp/test_sdp.log symlink so it points to the current log.
rm -f "$LogLink"
ln -s "$Log" "$LogLink"
msg "${H1}\\nLog is: $Log\\n"
fi
ThisUser=$(id -n -u)
msg "Started $ThisScript v$Version as $ThisUser@$ThisHost on $(date) as pid $$:\\nInitial Command Line:\\n$CmdLine\\n"
CfgFile="$CfgDir/test_sdp.$ThisHost.cfg"
if [[ -r "$CfgFile" ]]; then
# shellcheck disable=SC1090
source "$CfgFile" || bail "Failed to load config file [$CfgFile]."
else
bail "Missing config file [$CfgFile]."
fi
# Sanity check on values loaded from the config file.
[[ -z "$TestTag" ]] && bail "Environment loaded from $CfgFile is missing variable definition for TestTag."
if [[ $ThisHost == "$RunHost" ]]; then
msg "Verified: Running on $RunHost."
else
bail "Not configured to run on host $ThisHost. Run only on $RunHost, as configured in $CfgFile."
fi
msg "Loading and verifying command line test data from: $CLITestDataFile"
[[ -r "$CLITestDataFile" ]] || bail "Missing command line test data file [$CLITestDataFile]."
# See the $CLITestDataFile file for details on the expected file format.
# Short version: We're expecting one-line entries like this:
# <P4Cmd>|<ExpectedExitCode>|<TestLog>|<ExpectedStringRegex>|<Comments>
# The '<P4Cmd>' command *must* start with 'p4 '
Line=0; while read -r entry; do
Line=$((Line+1))
# shellcheck disable=SC2116
[[ -z "$(echo "$entry")" ]] && continue
[[ $entry == "#"* ]] && continue
# P4TestCmd is the first field, delimited by '|'.
P4TestCmd=${entry%%|*}
# ExpectedExit is the second field, delimited by '|'.
ExpectedExit=${entry#*|}
ExpectedExit=${ExpectedExit%%|*}
# TestLog is the third field, delimited by '|'.
TestLog=${entry#*|}
TestLog=${TestLog#*|}
TestLog=${TestLog%%|*}
# ExpectedString is the second from the last field; strip from the right.
ExpectedString=${entry%|*}
ExpectedString=${ExpectedString##*|}
# Comments is the last field. Strip from the left.
Comments=${entry##*|}
P4C="p4 -u bruno -c jam_ws"
if [[ $P4TestCmd == "p4 "* ]]; then
P4TestCmd=${P4TestCmd/p4 /$P4C }
elif [[ $P4TestCmd == "p4:"* ]]; then
# Parse entries like: "p4:user:client command args ..."
User=${P4TestCmd#p4:}
User=${User%%:*}
Workspace=${P4TestCmd#p4:}
Workspace=${Workspace#*:}
Workspace=${Workspace%% *}
P4TestCmd=${P4TestCmd/p4:$User:$Workspace /$P4 -u $User -c $Workspace }
else
errmsg "Entry on line $Line of $CLITestDataFile has a bogus 'p4' command. Must start with 'p4 '. Skipping this test."
CLITestDataOK=0
continue
fi
if [[ $ExpectedExit == U ]]; then
ExpectedExit=Undefined
else
if [[ $ExpectedExit =~ [0-9]+ ]]; then
ExpectedExitCode="$ExpectedExit"
else
errmsg "Entry on line $Line of $CLITestDataFile has a bogus exit code. Must be numeric or 'U', value is $ExpectedExit. Skipping this test."
CLITestDataOK=0
continue
fi
fi
msg "C:[$P4TestCmd] E:[$ExpectedExit] L:[$TestLog] S:[$ExpectedString] Comments: $Comments"
done < "$CLITestDataFile"
if [[ "$CLITestDataOK" -eq 1 ]]; then
msg "Verified: All test entries are OK."
else
if [[ "$RequireValidTests" -eq 1 ]]; then
bail "The command line test data file [$CLITestDataFile] contained invalid entries. Use '-f' to bypassing bad tests and continue."
else
msg "\\nSome tests were skipped due to invalid entries in $CLITestDataFile. Continuing due to '-f'.\\n"
fi
fi
msg "$H1\\nPre-start test preparations."
msg "Loading SDP Environment file."
# shellcheck disable=SC1091
source /p4/common/bin/p4_vars 1 ||\
bail "Failed to load SDP environment file p4_vars."
msg "$H3\\nExecuting Command Line Tests."
# It might seem inelegant to simply read-the config file in again, as opposed to storing and re-using the results
# from the data verification run above. But with 'bash', storing results tends to have bad but subtle side effects,
# like having quote characters disappear.
Line=0; while read -r entry; do
Line=$((Line+1))
# shellcheck disable=SC2116
[[ -z "$(echo "$entry")" ]] && continue
[[ $entry == "#"* ]] && continue
TestPassed=1
# P4TestCmd is the first field, delimited by '|'.
P4TestCmd=${entry%%|*}
# ExpectedExit is the second field, delimited by '|'.
ExpectedExit=${entry#*|}
ExpectedExit=${ExpectedExit%%|*}
# TestLog is the third field, delimited by '|'.
TestLog=${entry#*|}
TestLog=${TestLog#*|}
TestLog=${TestLog%%|*}
# ExpectedString is the second from the last field; strip from the right.
ExpectedString=${entry%|*}
ExpectedString=${ExpectedString##*|}
# Comments is the last field. Strip from the left.
Comments=${entry##*|}
if [[ "$TestLog" == "output" ]]; then
ExpectedStringInOutput=1
else
ExpectedStringInOutput=0
fi
Comments=${entry##*|}
if [[ $P4TestCmd == "p4 "* ]]; then
P4TestCmd=${P4TestCmd/p4 /$P4C }
elif [[ $P4TestCmd == "p4:"* ]]; then
# Parse entries like: "p4:user:client command args ..."
User=${P4TestCmd#p4:}
User=${User%%:*}
Workspace=${P4TestCmd#p4:}
Workspace=${Workspace#*:}
Workspace=${Workspace%% *}
P4TestCmd=${P4TestCmd/p4:$User:$Workspace /$P4 -u $User -c $Workspace }
else
TestSkipCount+=1
continue
fi
if [[ "$ExpectedExit" == U ]]; then
ExpectedExit=Undefined
else
if [[ "$ExpectedExit" =~ [0-9]+ ]]; then
ExpectedExitCode="$ExpectedExit"
else
TestSkipCount+=1
continue
fi
fi
TestCount=$((TestCount+1))
msg "$H2\\nTest $TestCount\\nTesting Command: $P4TestCmd\\nExpected Exit: $ExpectedExit\\nTest Log: $TestLog\\n"
if [[ "$ExpectedStringInOutput" -eq 1 ]]; then
msg "Expected string in output: $ExpectedString\\nComments: $Comments\\n"
else
msg "Expected string in log [$TestLog]: $ExpectedString\\nComments: $Comments\\n"
fi
if [[ "$NoOp" -eq 0 ]]; then
$P4TestCmd > "$OutputFile" 2>&1
ActualExitCode=$?
else
# No_p mode. Try to spoof successful test.
msg "NO_OP: Would have run: $P4TestCmd > $OutputFile 2>&1"
ActualExitCode="$ExpectedExitCode"
echo "$ExpectedString" > "$OutputFile"
fi
msg "\\n== Command Output =="
cat "$OutputFile"
msg "TEST_EXIT_CODE: Actual $ActualExitCode, Expected $ExpectedExit"
if [[ "$ExpectedExit" == U ]]; then
msg "Ignoring TEST_EXIT_CODE due to 'U' value."
else
[[ "$ExpectedExitCode" -ne "$ActualExitCode" ]] && TestPassed=0
fi
if [[ -n "$ExpectedString" ]]; then
# Check for the expected string in the command output or the cbd.log file.
if [[ "$ExpectedStringInOutput" -eq 1 ]]; then
if grep -E -q "$ExpectedString" "$OutputFile"; then
msg "\\nExpected string [$ExpectedString] found in command output."
else
msg "\\nExpected string [$ExpectedString] NOT found in command output."
TestPassed=0
fi
else
if [[ -r "$TestLog" ]]; then
if grep -q -E "$ExpectedString" "$TestLog"; then
msg "\\nExpected string [$ExpectedString] found in log [$TestLog]."
else
msg "\\nExpected string [$ExpectedString] NOT found in log [$TestLog]."
TestPassed=0
fi
else
msg "String [$ExpectedString] expected in log [$TestLog] but log is missing."
TestPassed=0
fi
fi
else
msg "No expected string defined, skipping check for expected string for this test."
fi
if [[ "$TestPassed" -eq 1 ]]; then
msg "\\nPASS TEST $TestCount\\n"
TestPassCount=$((TestPassCount+1))
else
msg "\\nFAIL TEST $TestCount\\n"
TestFailCount=$((TestFailCount+1))
fi
done < "$CLITestDataFile"
if [[ "$TestPassCount" -ge 1 && "$TestFailCount" -eq 0 && "$ErrorCount" -eq 0 ]]; then
msg "${H1}\\nALL $TestCount tests PASSED.\\n"
else
if [[ "$ErrorCount" -eq 0 ]]; then
msg "There were no errors in test setup. Test results should be valid."
else
errmsg "There were $ErrorCount errors in test setup. Test results may not be valid."
fi
msg "${H1}\\nTest Run completed. Summary:\\nTests Executed (Passed/Failed/Skipped): $TestCount ($TestPassCount/$TestFailCount/$TestSkipCount)\\n\\nScan above output carefully.\\n"
fi
# Illustrate using $SECONDS to display runtime of a script.
msg "That took about $((SECONDS/3600)) hours $((SECONDS%3600/60)) minutes $((SECONDS%60)) seconds.\n"
# See the terminate() function, which is really where this script exits.
exit "$((ErrorCount+TestFailCount+TestSkipCount))"
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #15 | 31522 | C. Thomas Tyler | Added color to BSW test suite scripts. | ||
| #14 | 31514 | C. Thomas Tyler | Added P4 environment isolation logic. | ||
| #13 | 31213 | C. Thomas Tyler | Fixed test suite issue with CLI tests formatted as p4@PORT@USER@CLIENT. | ||
| #12 | 31163 | C. Thomas Tyler |
Added support for specifying port/user/client in individual CLI tests. |
||
| #11 | 30981 | C. Thomas Tyler |
Enhanced to make host-specific configuration files optional, making the test suite more easily extended to other purposes. |
||
| #10 | 30748 | C. Thomas Tyler | Added TestGroup to output related to test descriptions. | ||
| #9 | 30747 | C. Thomas Tyler |
Added Test Group concept to run_cli_tests.sh, with same meaning and usage as in run_scripted_tests.sh. The goal is to allow running a defined subset of tests. |
||
| #8 | 30742 | C. Thomas Tyler | Added support for specifying exit code as 'N' for non-zero. | ||
| #7 | 30711 | C. Thomas Tyler | Added support for 'no_cmd' test. | ||
| #6 | 30710 | C. Thomas Tyler |
Enhanced method of calling tests to use 'eval' with quoted string, making it work with a wider array of command lines to test with. |
||
| #5 | 30695 | C. Thomas Tyler |
Added '-cd <cfg_dir>' option to jailbreak these tests scripts; they can now be used for more general testing. Added '-e' option to run_cli_tests.sh, same as '-e' that already exists in run_scripted_tests.sh. Added '-no_env' option to both scripts. Added '-sp' to skip preflight checks in run_scripted_tests.sh. |
||
| #4 | 30686 | C. Thomas Tyler |
Enhanced CLI tests to be able to call arbitrary command lines rather than just p4 commands. |
||
| #3 | 30648 | C. Thomas Tyler |
For run_scripts_tests.sh, added '-e' option to abort on first test failure. Updated and corrected documented command line options, removing obsolete options. |
||
| #2 | 30623 | C. Thomas Tyler |
Added feature for scripted test to run addtional checks from an earlier execution of a script. Added more tests. |
||
| #1 | 30622 | C. Thomas Tyler | Added files for initial SDP BSW test suite. |