#!/bin/bash
#------------------------------------------------------------------------------
set -u
#==============================================================================
# Declarations and Environment Setup
#------------------------------------------------------------------------------
declare ThisScript=${0##*/}
declare Version="1.5.1"
declare Log=
declare Verbosity=3
declare HxDepots=
declare ResetDir=
declare SDPBranch=${SDP_BRANCH:-Unset}
declare HelixInstallerTestDir=
declare H1="\\n=============================================================================="
declare H2="\\n------------------------------------------------------------------------------"
declare -i ErrorCount=0
declare -i TestID=0
declare -i SilentMode=0
declare -i OverallExitCode=0
declare -i Debug=0
declare -i TestCrontab=1
declare TestLog=
# The OS environment variable should be set in the Docker configuration.
export OS="${OS:-UnsetOS}"
#==============================================================================
# Local Functions
function msg () { echo -e "$*"; }
function vmsg () { [[ "$Verbosity" -ge 4 ]] && msg "$*"; }
function vvmsg () { [[ "$Verbosity" -ge 5 ]] && msg "$*"; }
function dbg () { [[ "$Debug" -eq 1 ]] && msg "$*"; }
function errmsg () { msg "\\nError: ${1:-Unknown Error}\\n"; ErrorCount+=1; }
function bail () { errmsg "BAILING: ${1:-Unknown Error}"; exit "${2:-255}"; }
function run () {
dbg "CALL run (${1:-}, ${2:-})"
local cmdAndArgs="${1:-echo}"
local desc="${2:-}"
[[ -n "$desc" ]] && msg "$desc"
msg "Running: $cmdAndArgs"
$cmdAndArgs
return $?
}
#------------------------------------------------------------------------------
# Function: usage (required function)
#
# Input:
# $1 - style, either -h (for short form) or -man (for man-page like format).
# The default is -h.
#
# $2 - error message (optional). Specify this if usage() is called due to
# user error, in which case the given message displayed first, followed by the
# standard usage message (short or long depending on $1). If displaying an
# errror, usually $1 should be -h so that the longer usage message doesn't
# obsure the error message.
#
# Sample Usage:
# usage
# usage -h
# usage -man
# usage -h "Incorrect command line usage."
#------------------------------------------------------------------------------
function usage
{
dbg "CALL usage (${1:-}, ${2:-})"
declare style="${1:--h}"
declare errorMessage="${2:-Unset}"
if [[ "$errorMessage" != "Unset" ]]; then
errmsg "Bad Usage:\\n\\n$errorMessage\\n\\n"
fi
msg "USAGE for $ThisScript v$Version:
$ThisScript [-v<n>] [-d|-D]
or
$ThisScript [-h|-man|-V]
"
if [[ $style == -man ]]; then
msg "
DESCRIPTION:
This is the test suite for the Helix Installer.
OPTIONS:
-v<n> Set verbosity 1-5 (-v1 = quiet, -v5 = highest).
Levels 1 and 2 are reserved for future use.
See EXAMPLES below for more detail on -v3, -v4, and -v5.
-L <log>
Specify the path to a log file to enable logging. Or, specify
'-L off' to disable logging using a default log file name:
/tmp/$ThisScript.v$Version.<datestamp>.log
NOTE: This script is self-logging if logging is enabled. That is,
output displayed on the screen is simultaneously captured in the log
file. Do not run this script with redirection operators like '> log'
or '2>&1', and do not use '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.
HELP OPTIONS:
-h Display short help message
-man Display man-style help message
-V Dispay version.
EXIT CODE:
If all tests are executed and all pass, the exit code is 0.
If all tests are executed and some fail, the exit code is 1.
If '-h' or '-man' (usage messages) are used, the exit code is 1.
If there is a usage error to this script or something goes
horribly wrong with testing, the exit code is 255.
EXAMPLES:
Example 1: Test Summary
Normally, run with no arguments (equivalent to -v3):
$ThisScript
This verbosity level is mainly intended to get a quick summary of which
tests pass or fail, but without detail as to why they failed.
Example 2: A Bit More
For a bit more pedantic output:
$ThisScript -v4
This verbosity level shows the test summary, plus a bit more detail
about output we're expecting in each test. At this level, command
output is suppressed except in cases where expected output for a
test is defined but not contained in the output.
Example 3: Full Details
For a lot more pedantic output:
$ThisScript -v5
This verbosity level shows the test summary, plus a bit more detail
about output we're expecting in each test, plus the full output of
all commands, regardless of whether they passed or failed the tests.
" | less
fi
if [[ "$errorMessage" != "Unset" ]]; then
exit 255
else
exit 1
fi
}
#------------------------------------------------------------------------------
# Function run_test_cmd (cmd, desc, expectedExitCode, expectedInOutput)
#
# Runs a test command and determines test PASS/FAIL status.
# Increments global $TestID, and sets $TestLog.
# Increments global $ErrorCount if the test fails.
#
# After running a test command, the $TestLog can be checked multiple times
# (e.g with grep commands) until the next test resets it. Test logs are
# /tmp/test.<TestID>.log, so it is possible to grep for something in all
# logs as well.
#
# $1 - command to execute; required
# $2 - description of test
# $3 - expected exit code; default 0
# $4 - snippet of text to 'grep -q -E' for in output to pass.
# The '-E' allows regex pattern greps, e.g. for "(this|that)"
#
# Test can only pass if command executes and returns expected exit code.
# If 4th parameter $expectedInOutput is defined, in addition to having the
# expected exit code, output must also contain the expected string for test
# to pass.
#------------------------------------------------------------------------------
function run_test_cmd () {
dbg "CALL run_test_cmd (${1:-T} ${2:-}, ${3:-}, ${4:-})"
local testCmd="${1:-Unset}"
local desc="${2:-}"
local -i expectedExitCode="${3:-0}"
local expectedInOutput="${4:-}"
local -i exitCode=0
[[ "$testCmd" == Unset ]] && bail "TEST ERROR: No test specified."
TestID+=1
TestLog="/tmp/test.$TestID.log"
msg "${H2}\\nTEST $TestID: $desc\\nRunning:\\n$testCmd\\nExpected Exit Code: $expectedExitCode"
[[ -n "$expectedInOutput" ]] && msg "Expected in Output: $expectedInOutput"
msg "Test log: $TestLog"
$testCmd > "$TestLog" 2>&1
exitCode="$?"
msg "${H2}\\nBEGIN TEST $TestID OUTPUT"
cat "$TestLog"
msg "END TEST $TestID OUTPUT\\n"
if [[ "$exitCode" -ne "$expectedExitCode" ]]; then
errmsg "Exit code was $exitCode, but expected to be $expectedExitCode."
msg "FAILED TEST $TestID"
return 1
else
vmsg "Exit code was $expectedExitCode, as expected."
fi
if [[ -n "$expectedInOutput" ]]; then
# This 'grep' should never generate stderr, so we do NOT redirect stderr
# here, just in case something goes insanely wrong with the testing
# mechanism itself. The exit code determines the success/failure of
# the test.
if grep -q -E "$expectedInOutput" "$TestLog"; then
vmsg "Expected output detected: $expectedInOutput"
else
errmsg "MISSING expected output: $expectedInOutput"
msg "FAILED TEST $TestID"
return 1
fi
else
vmsg "No expected output defined for test $TestID. Skipping output grep check."
fi
msg "PASSED TEST $TestID"
return 0
}
#------------------------------------------------------------------------------
# Function run_su_test_cmd (cmd, user, desc, expectedExitCode, expectedInOutput)
#
# Runs a test command with su -u 'user' and determines test PASS/FAIL status.
# Increments global $TestID, and sets $TestLog.
# Increments global $ErrorCount if the test fails.
#
# After running a test command, the $TestLog can be checked multiple times
# (e.g with grep commands) until the next test resets it. Test logs are
# /tmp/test.<TestID>.log, so it is possible to grep for something in all
# logs as well.
#
# $1 - command to execute; required
# $2 - operating system user to run as
# $3 - description of test
# $4 - expected exit code; default 0
# $5 - snippet of text to 'grep -q -E' for in output to pass.
# The '-E' allows regex pattern greps, e.g. for "(this|that)"
#
# Test can only pass if command executes and returns expected exit code.
# If 5th parameter $expectedInOutput is defined, in addition to having the
# expected exit code, output must also contain the expected string for test
# to pass.
#------------------------------------------------------------------------------
function run_test_su_cmd () {
dbg "CALL run_test_su_cmd (${1:-}, ${2:-}, ${3:-}, ${4:-}, ${5:-})"
local testCmd="${1:-Unset}"
local user="${2:-Unset}"
local desc="${3:-}"
local -i expectedExitCode="${4:-0}"
local expectedInOutput="${5:-}"
local -i exitCode=0
[[ "$testCmd" == Unset ]] && bail "TEST ERROR: No test specified."
[[ "$user" == Unset ]] && bail "TEST ERROR: No user specified."
TestID+=1
TestLog="/tmp/test.$TestID.log"
msg "${H2}\\nTEST $TestID: $desc\\nRunning as $user:\\n$testCmd\\nExpected Exit Code: $expectedExitCode"
[[ -n "$expectedInOutput" ]] && msg "Expected in Output: $expectedInOutput"
msg "Test log: $TestLog"
su - "$user" -c "$testCmd" > "$TestLog" 2>&1
exitCode="$?"
msg "${H1}\\nBEGIN TEST $TestID OUTPUT"
cat "$TestLog"
msg "END TEST $TestID OUTPUT\\n"
if [[ "$exitCode" -ne "$expectedExitCode" ]]; then
errmsg "Exit code was $exitCode, but expected to be $expectedExitCode."
msg "FAILED TEST $TestID"
return 1
else
vmsg "Exit code was $expectedExitCode, as expected."
fi
if [[ -n "$expectedInOutput" ]]; then
# This 'grep' should never generate stderr, so we do NOT redirect stderr
# here, just in case something goes insanely wrong with the testing
# mechanism itself. The exit code determines the success/failure of
# the test.
if grep -q -E "$expectedInOutput" "$TestLog"; then
vmsg "Expected output detected: $expectedInOutput"
else
errmsg "MISSING expected output: $expectedInOutput"
msg "FAILED TEST $TestID"
return 1
fi
else
vmsg "No expected output defined for test $TestID. Skipping output grep check."
fi
msg "PASSED TEST $TestID"
return 0
}
#------------------------------------------------------------------------------
# Function check_log_test (expectedInLog)
#
# Increments global $TestID, and reads $TestLog.
# Increments global $ErrorCount if the test fails.
#
# After running a test command with run_test_cmd() or run_test_su_cmd(), the
# the $TestLog can be checked multiple times with grep commands using
# check_log_test.
#
# $1 - snippet of text to 'grep -q -E' for in output to pass.
# The '-E' allows regex pattern greps, e.g. for "(this|that)"
#------------------------------------------------------------------------------
function check_log_test () {
dbg "CALL check_log_test (${1:-})"
local expectedInLog="${1:-Unset}"
[[ "$expectedInLog" == Unset ]] && bail "TEST ERROR: No grep text specified."
TestID+=1
if grep -q -E "$expectedInLog" "$TestLog"; then
vmsg "Expected output detected: $expectedInLog"
else
errmsg "MISSING expected output: $expectedInLog"
msg "FAILED TEST $TestID"
return 1
fi
msg "PASSED TEST $TestID"
return 0
}
#------------------------------------------------------------------------------
# Function terminate, run on termination.
#------------------------------------------------------------------------------
function terminate () {
trap - EXIT SIGINT SIGTERM
[[ "$Log" == "off" ]] || msg "\\nLog is: $Log${H2}"
}
#==============================================================================
# Command Line Parsing
declare -i shiftArgs=0
set +u
while [[ $# -gt 0 ]]; do
case $1 in
(-h) usage -h;;
(-man) usage -man;;
(-V) msg "$ThisScript v$Version"; exit 1;;
(-v1) Verbosity=1;;
(-v2) Verbosity=2;;
(-v3) Verbosity=3;;
(-v4) Verbosity=4;;
(-v5) Verbosity=5;;
(-L) Log="$2"; shiftArgs=1;;
(-si) SilentMode=1;;
(-d) Debug=1;; # Debug
(-D) Debug=1; set -x;; # Debug; use 'set -x' mode.
(*) usage -h "Unknown arg ($1).";;
esac
# Shift (modify $#) the appropriate number of times.
shift; while [[ $shiftArgs -gt 0 ]]; do
[[ $# -eq 0 ]] && usage -h "Incorrect number of arguments."
shiftArgs=$shiftArgs-1
shift
done
done
set -u
[[ "${Log:-Unset}" == "off" && "$SilentMode" -eq 1 ]] &&\
bail "Usage Error: The '-L off' and '-si' options are mutually exclusive."
[[ "${Log:-Unset}" == "Unset" ]] && \
Log="/tmp/${ThisScript%.sh}.v$Version.$(date +'%Y%m%d-%H%M%S').log"
#==============================================================================
# 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
msg "Logging to: $Log"
fi
msg "${H1}\\nStarted ${0##*/} v$Version as $(whoami) on $OS at $(date)."
msg "$H1\\nTest Prep - Part 1."
if [[ "$OS" == "centos6" ]]; then
msg "Note: Skipping crontab testing on $OS due to test env setup issues."
TestCrontab=0
fi
# The SDP_BRANCH variable is passed in via the Docker environment, and can be set
# locally on the VM for manual testing.
if [[ "$SDPBranch" != Unset ]]; then
msg "\\nInfo: SDP $SDPBranch selected for testing."
else
SDPBranch="main"
msg "\\nWarning: No SDP branch selected for testing; defaulting to $SDPBranch branch."
fi
HxDepots="hxdepots"
ResetDir="/$HxDepots/reset"
HelixInstallerSrcDir="/hi/src"
HelixInstallerTestDir="/hi/test"
msg "Ensuring required utilities are available."
if ! command -v rsync > /dev/null; then
msg "Missing rsync, trying to get it."
if [[ -r /etc/redhat-release ]]; then
run "yum install -y rsync" "Installing package rsync" ||\
bail "Failed to get package rsync."
elif [[ -r /etc/lsb-release ]]; then
run "apt-get install -y rsync" "Installing package rsync." ||\
bail "Failed to get package rsync."
else
bail "Required rsync utility is missing, not sure how to get it on this OS."
fi
fi
if [[ "$TestCrontab" -eq 1 ]]; then
if ! command -v crontab > /dev/null; then
msg "Missing crontab, trying to get it."
if [[ -r /etc/redhat-release ]]; then
run "yum install -y cronie" "Installing package cronie" ||\
bail "Failed to get package cronie."
elif [[ -r /etc/lsb-release ]]; then
run "apt-get install -y cron" "Installing package cron." ||\
bail "Failed to get package cron."
else
bail "Required crontab utility is missing, not sure how to get it on this OS."
fi
fi
fi
run "mkdir /$HxDepots" || bail "Failed to mkdir /$HxDepots"
run "rsync -a $HelixInstallerSrcDir/ $ResetDir" || bail "Failed to rsync to: $ResetDir"
cd "$ResetDir" || "Could not cd to: $ResetDir"
msg "Operating in: $PWD"
run "cp -pf $HelixInstallerTestDir/settings.fgs.cfg $ResetDir/." || \
bail "Failed to copy cfg file to: $ResetDir"
msg "$H1\\nStarting Version check test."
msg "Cleaning logs from earlier runs (if any)."
if ! rm -f /tmp/test.*.log; then
bail "Failed to remove logs from earlier logs with: rm -f /tmp/test.*.log"
fi
run_test_cmd './reset_sdp.sh -h' 'Version' 1 'reset_sdp.sh v'
run_test_cmd './reset_sdp.sh -man' 'Manual Page' 1 'DESCRIPTION:'
msg "$H1\\nNormal Flow Tests - fgs.\\n"
if [[ "$TestCrontab" -eq 1 ]]; then
run_test_cmd "./reset_sdp.sh -b $SDPBranch -c settings.fgs.cfg -no_ssl -no_sd -no_ppr -no_tweaks -no_systemd -v -M" \
'Basic usage test for instance fgs' 0 'SUCCESS'
else
run_test_cmd "./reset_sdp.sh -b $SDPBranch -c settings.fgs.cfg -fast -no_ssl -no_sd -no_ppr -no_cron -no_tweaks -no_systemd -v -M" \
'Basic usage test for instance fgs' 0 'SUCCESS'
run_test_cmd 'cat /etc/sudoers.d/cooldude' 'Limited sudoers check' 0 'Cmnd_Alias P4_SVC'
fi
run_test_cmd 'id -u cooldude' 'Checking that OS user cooldude got created.' 0
run_test_cmd '/p4/common/bin/p4master_run fgs p4 set -q P4USER' \
'Confirming P4USER is superman.' 0 'superman'
if [[ "$TestCrontab" -eq 1 ]]; then
run_test_su_cmd 'crontab -l' 'cooldude' \
'Checking crontab of OSUSER cooldude.' 0 'P4AdminList'
fi
msg "$H1\\nTest Prep - Part 2."
msg "Operating in: $PWD"
run_test_cmd "./reset_sdp.sh -X -R -c settings.fgs.cfg" \
'Test of Extreme Cleanup with -X and -R flags' 0 'Extreme Cleanup complete'
msg "$H1\\nTest Prep - Part 3."
HxDepots="bigdisk"
ResetDir="/$HxDepots/reset"
run "mkdir /$HxDepots" || bail "Failed to mkdir /$HxDepots"
run "rsync -a $HelixInstallerSrcDir/ $ResetDir" || bail "Failed to rsync to: $ResetDir"
cd "$ResetDir" || "Could not cd to: $ResetDir"
msg "Operating in: $PWD"
run "cp -pf $HelixInstallerTestDir/settings.fgs2.cfg $ResetDir/." || \
bail "Failed to copy cfg file to: $ResetDir"
msg "$H1\\nNormal Flow Tests - fgs2.\\n"
run_test_cmd "./reset_sdp.sh -b $SDPBranch -c settings.fgs2.cfg -fast -no_ssl -no_sd -no_ppr -no_cron -no_tweaks -no_systemd -no_firewall -no_sudo -v" \
'Basic usage test for instance fgs2' 0 'SUCCESS'
check_log_test "Renamed to clarify this should not be executed again."
check_log_test "Skipping sudo setup due to"
check_log_test "Skipping firewall setup due"
run_test_cmd 'id -u cooldude' 'Checking that OS user cooldude got created.' 0
run_test_cmd 'id -g -n cooldude' 'Checking that OS user cooldude is in group heroes.' 0 heroes
run_test_cmd '/p4/common/bin/p4master_run fgs2 p4 set -q P4USER' \
'Confirming P4USER is superman.' 0 'superman'
# Quiet Test Prep: Re-activate the reset_sdp.sh script.
mv reset_sdp.sh.txt reset_sdp.sh
chmod +x reset_sdp.sh
run_test_cmd "./reset_sdp.sh -b $SDPBranch -c settings.fgs2.cfg -fast -no_ssl -no_sd -no_ppr -no_cron -no_tweaks -no_systemd -no_sudo -ls -v" \
'Bad usage test with -no_sudo and -ls.' 1 'options are mutually exclusive'
if [[ "$ErrorCount" -eq 0 ]]; then
msg "$H1\\nSummary: Executed $TestID tests, ALL PASSED."
else
msg "$H1\\nSummary: Executed $TestID tests, $ErrorCount FAILED, $((TestID-ErrorCount)) passed."
OverallExitCode=1
fi
msg "\\nTest suite executed in $((SECONDS/3600)) hours $((SECONDS%3600/60)) minutes $((SECONDS%60)) seconds.\n"
exit "$OverallExitCode"
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #4 | 30414 | C. Thomas Tyler |
Released SDP 2024.1.30412 (2024/06/13). Copy Up using 'p4 copy -r -b perforce_software-helix-installer-dev'. |
||
| #3 | 28665 | C. Thomas Tyler |
Released SDP 2022.1.28663 (2022/03/08). Copy Up using 'p4 copy -r -b perforce_software-helix-installer-dev'. |
||
| #2 | 28129 | C. Thomas Tyler |
Released SDP 2021.3.28126 (2021/10/24). Copy Up using 'p4 copy -r -b perforce_software-helix-installer-dev'. |
||
| #1 | 27427 | C. Thomas Tyler |
Released SDP 2021.2.27425 (2021/02/09). Copy Up using 'p4 copy -r -b perforce_software-helix-installer-dev'. |
||
| //guest/perforce_software/helix-installer/dev/test/test_hi.sh | |||||
| #8 | 27419 | C. Thomas Tyler |
Added '-v' option to run verify_sdp.sh with various options. Added test and doc for same. The options to verify_sdp.sh are dependent on options to reset_sdp.sh. Enabled configure_sample_depot_for_sdp.sh to work with non-default mount points. |
||
| #7 | 27390 | C. Thomas Tyler |
Added check_log_test() to check log for text from executed command. Added test for limited sudo. Added test for renaming of reset_sdp.sh script. |
||
| #6 | 27388 | C. Thomas Tyler |
test_hi.sh v1.3.0: * Improved log handling so test logs remain available for add'l grep checks. * Improved in-code docs. |
||
| #5 | 27306 | C. Thomas Tyler |
Adjusted to skip 'crontab' tests on centos6 due to complexity setting up crontab on the Docker image for platform, possibly due to it being EOL already. |
||
| #4 | 27304 | C. Thomas Tyler | Test suite robustness improvements, added cront tests back in. | ||
| #3 | 27299 | C. Thomas Tyler | Added '-H <hostname>' and '-T <timezone>' options and cfg settings. | ||
| #2 | 27289 | C. Thomas Tyler |
Added more tests to test suite. These work with '-o centos7'. |
||
| #1 | 27284 | C. Thomas Tyler |
Added Docker test suite to displace Vagrant. Initial version passes with options: hits.sh -o centos7 |
||