#!/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 -i ScriptedTestDataOK=0
declare -i AllScriptedTestDataOK=1
declare -i i=0
declare H1="=============================================================================="
declare H2="------------------------------------------------------------------------------"
declare H3="||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
declare Log=
declare LogLink="/tmp/${ThisScript%.sh}.log"
# Values loaded from general config file.
declare RunHost=
declare TestTag=
declare ExpectedExit=
declare TestLog=
declare TestLogHost=
declare -i RequireValidTests=1
declare -i ExpectedExitCode
declare -i ActualExitCode
declare ExpectedString=
declare -i ExpectedStringInOutput=0
declare Comments=
declare CfgDir=/p4/sdp/test/bsw
declare CfgFile=
declare -a TestScriptList
declare -a ExpectedExitList
declare -a TestLogList
declare -a ExpectedStringList=
declare -a TestLogHostList
declare -a CommentsList
declare ScriptedTestDataFile="$CfgDir/scripted_tests.cfg"
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 {
# Disable signal trapping.
trap - EXIT SIGINT SIGTERM
dbg "$ThisScript: EXITCODE: $ErrorCount"
# 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 scripted_test_data.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 "$H1\\nPre-start test preparations."
# shellcheck disable=SC1091
source /p4/common/bin/p4_vars 1 ||\
bail "Failed to load SDP environment file p4_vars."
msg "Loaded SDP Environment file. Logging in."
msg "Loading and verifying scripted test data from: $ScriptedTestDataFile"
[[ -r "$ScriptedTestDataFile" ]] || bail "Missing command line test data file [$ScriptedTestDataFile]."
# See the file scripted_tests.cfg for details on expected format of entries
# in that file we are about to parse. Short version is that we're expecting
# lines of this form:
#
# <TestScript>|<ExitCode>|[<Host>:]<TestLog>|<ExpectedStringRegex>|<Comments>
AllScriptedTestDataOK=1
Line=0; while read -r entry; do
Line=$((Line+1))
# Ignore blank lines and comments (but still increment line counter).
# shellcheck disable=SC2116
[[ -z "$(echo "$entry")" ]] && continue
[[ $entry == "#"* ]] && continue
ScriptedTestDataOK=1
# TestScript is the first field.
if [[ "$entry" =~ ^no_script ]]; then
TestScript="no_script"
ExpectedExit="U"
else
TestScript="$CfgDir/${entry%%|*}"
# ExpectedExit is 2nd field.
ExpectedExit="${entry#*|}"
ExpectedExit="${ExpectedExit%%|*}"
fi
# TestLog is 3rd field.
TestLog="${entry#*|}"
TestLog="${TestLog#*|}"
TestLog="${TestLog%%|*}"
# TestLog might look like syd-helix-04:/p4/1/logs/log, indicating to grep the log on
# host syd-helix-04.
if [[ "$TestLog" == *:* ]]; then
TestLogHost="${TestLog%%:*}"
TestLog="${TestLog##*:}"
else
TestLogHost=
fi
# ExpectedString is 4th field.
ExpectedString="${entry#*|}"
ExpectedString="${ExpectedString#*|}"
ExpectedString="${ExpectedString#*|}"
ExpectedString="${ExpectedString%%|*}"
# Comments is right-most field; strip everything to the left.
Comments="${entry##*|}"
if [[ -n "$TestLogHost" ]]; then
msg "S:[$TestScript] E:[$ExpectedExit] L:[${TestLogHost}:$TestLog] S:[$ExpectedString]\\nComments: $Comments"
else
msg "S:[$TestScript] E:[$ExpectedExit] L:[$TestLog] S:[$ExpectedString]\\nComments: $Comments"
fi
# Do sanity tests on the loaded scripted test configuration data.
if [[ "$ExpectedExit" == U || "$ExpectedExit" =~ ^[0-9]+$ ]]; then
dbg "ExpectedExit value [$ExpectedExit] is valid."
else
errmsg "Invalid format of expected exit code [$ExpectedExit] on line $Line of $ScriptedTestDataFile; should be numeric or 'U'."
ScriptedTestDataOK=0
fi
if [[ ! "$TestLog" == /* ]]; then
errmsg "Invalid format of TestLog [$TestLog] on line $Line of $ScriptedTestDataFile; absolute path expected."
ScriptedTestDataOK=0
fi
if [[ "$TestScript" != no_script ]]; then
if [[ ! -x "$TestScript" ]]; then
errmsg "Missing or Non-Executable Test Script file [$TestScript]."
TestPassed=0
fi
fi
if [[ "$ScriptedTestDataOK" -eq 1 ]]; then
TestScriptList[TestCount]="$TestScript"
ExpectedExitList[TestCount]="$ExpectedExit"
TestLogList[TestCount]="$TestLog"
TestLogHostList[TestCount]="$TestLogHost"
ExpectedStringList[TestCount]="$ExpectedString"
CommentsList[TestCount]="$Comments"
TestCount+=1
else
TestSkipCount+=1
AllScriptedTestDataOK=0
fi
done < "$ScriptedTestDataFile"
if [[ "$AllScriptedTestDataOK" -eq 1 ]]; then
msg "Verified: All test entries are OK."
else
if [[ "$RequireValidTests" -eq 1 ]]; then
bail "The scripted test data file [$ScriptedTestDataFile] contained invalid entries. Use '-f' to bypass bad tests and continue using the good ones."
else
msg "\\n$TestSkipCount tests will be skipped due to invalid entries in $ScriptedTestDataFile. Continuing due to '-f'.\\n"
fi
fi
msg "${H3}\\nExecuting Scripted Tests."
[[ -r "$ScriptedTestDataFile" ]] || bail "Missing scripted test config file [$ScriptedTestDataFile]."
for ((i=0; i<TestCount; i++)); do
TestPassed=1
TestScript="${TestScriptList[i]}"
ExpectedExit="${ExpectedExitList[i]}"
ExpectedString="${ExpectedStringList[i]}"
TestLog="${TestLogList[i]}"
TestLogHost="${TestLogHostList[i]}"
Comments="${CommentsList[i]}"
if [[ "$ExpectedExit" == U ]]; then
ExpectedExit="Undefined"
else
ExpectedExitCode="$ExpectedExit"
fi
if [[ "$TestLog" == "output" ]]; then
ExpectedStringInOutput=1
else
ExpectedStringInOutput=0
fi
msg "$H2\\nTest $TestCount"
if [[ -n "$TestLogHost" ]]; then
msg "S:[$TestScript] E:[$ExpectedExit] L:[${TestLogHost}:$TestLog] S:[$ExpectedString]\\nComments: $Comments"
else
msg "S:[$TestScript] E:[$ExpectedExit] L:[$TestLog] S:[$ExpectedString]\\nComments: $Comments"
fi
TestScriptCmd="$TestScript $TestCount"
if [[ "$TestScript" != no_script ]]; then
if [[ "$NoOp" -eq 0 ]]; then
$TestScriptCmd > "$OutputFile" 2>&1
ActualExitCode=$?
else
msg "NO_OP: Would run: $TestScriptCmd > $OutputFile 2>&1"
echo > "$OutputFile"
ActualExitCode=0
fi
msg "\\n== Test Script Output =="
cat "$OutputFile"
msg "TEST_EXIT_CODE: Actual $ActualExitCode, Expected $ExpectedExit"
if [[ "$ExpectedExit" == U ]]; then
if [[ "$ActualExitCode" -ne 2 ]]; then
msg "Ignoring TEST_EXIT_CODE due to 'U' value."
else
msg "Test exit code 2 indicates test did not run correctly. Failing test."
TestPassed=0
fi
else
[[ "$ExpectedExitCode" -ne "$ActualExitCode" ]] && TestPassed=0
fi
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 -q -E "$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 [[ -n "$TestLogHost" ]]; then
if ssh -q "$TestLogHost" "[[ -r $TestLog ]]"; then
if ssh -q "$TestLogHost" "grep -q -E \"$ExpectedString\" $TestLog"; then
msg "\\nExpected string [$ExpectedString] found in log [${TestLogHost}:$TestLog]."
else
msg "\\nExpected string [$ExpectedString] NOT found in log [${TestLogHost}:$TestLog]."
fi
else
msg "String [$ExpectedString] expected in log [${TestLogHost}:$TestLog], but that log is missing."
TestPassed=0
fi
else
if [[ -r "$TestLog" ]]; then
if grep -E -q "$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 that log is missing."
TestPassed=0
fi
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
if [[ "$TestPassCount" -ge 1 && "$TestFailCount" -eq 0 && "$ErrorCount" -eq 0 ]]; then
msg "${H1}\\nALL $TestCount tests PASSED.\\n"
else
msg "${H1}\\nTest Run completed. Summary:
Total Tests: $TestCount
PASS Count: $TestPassCount
FAIL Count: $TestFailCount"
if [[ "$TestSkipCount" -ne 0 ]]; then
msg "SKIP Count: $TestSkipCount"
fi
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 "\\nScan above output carefully."
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 | |
|---|---|---|---|---|---|
| #19 | 31522 | C. Thomas Tyler | Added color to BSW test suite scripts. | ||
| #18 | 31514 | C. Thomas Tyler | Added P4 environment isolation logic. | ||
| #17 | 31306 | C. Thomas Tyler | Doc tweak; non-functional change. | ||
| #16 | 31073 | C. Thomas Tyler | Added sleep delay. | ||
| #15 | 31043 | C. Thomas Tyler |
Add scripted test for live checkpoint off commit (i.e. on edge, standby). |
||
| #14 | 30981 | C. Thomas Tyler |
Enhanced to make host-specific configuration files optional, making the test suite more easily extended to other purposes. |
||
| #13 | 30756 | C. Thomas Tyler | Completed support for '-no_env'. | ||
| #12 | 30748 | C. Thomas Tyler | Added TestGroup to output related to test descriptions. | ||
| #11 | 30742 | C. Thomas Tyler | Added support for specifying exit code as 'N' for non-zero. | ||
| #10 | 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. |
||
| #9 | 30676 | C. Thomas Tyler | Added '-FL' option to capture full logs. | ||
| #8 | 30674 | C. Thomas Tyler |
Added '-g' option to run only a specified test group. This requires a 'TestGroup' field in the scripted_tests.cfg file. |
||
| #7 | 30671 | C. Thomas Tyler |
Added support for checking test script raw output (rather than log file) for scripted tests. |
||
| #6 | 30651 | C. Thomas Tyler | Added preflight checks to ensure BSW Labs are running before starting test suite. | ||
| #5 | 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. |
||
| #4 | 30642 | C. Thomas Tyler |
Adjusted so rsync copies the target of the symlink (for log files) rather than the raw symlink. |
||
| #3 | 30626 | C. Thomas Tyler | Refined test harness logic and added more tests. | ||
| #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. |