Chore: Updates to improve overall robustness of robot framework tooling

Improved scripts to better meet modern coding best practices. Improved
many script functions and behaviours to allow some code to run outside
Jenkins jobs. Addressed many warnings produced by linting tools.

Issue-ID: CCSDK-3820
Signed-off-by: Matthew Watkins <mwatkins@linuxfoundation.org>
Change-Id: I2e4f702a0b58cbace341c31669b4ffcb023d499c
diff --git a/jjb/integration/include-raw-integration-install-robotframework-py3.sh b/jjb/integration/include-raw-integration-install-robotframework-py3.sh
index 6d0b496..efeb0f7 100644
--- a/jjb/integration/include-raw-integration-install-robotframework-py3.sh
+++ b/jjb/integration/include-raw-integration-install-robotframework-py3.sh
@@ -10,24 +10,70 @@
 ##############################################################################
 # vim: sw=4 ts=4 sts=4 et ft=sh :
 
-set -eu pipefail
+set -euxo pipefail
 
-# shellcheck disable=SC1090
-. ~/lf-env.sh
+echo "---> install-robotframework-py3.sh"
 
-# Create a virtual environment for robot tests and make sure setuptools & wheel
-# are up-to-date in addition to pip
-lf-activate-venv --python python3 --venv-file "${WORKSPACE}/.robot3_venv" \
-    setuptools \
-    wheel
+### Common variables
 
-# Save the virtual environment in ROBOT_VENV
-ROBOT3_VENV="$(cat "${WORKSPACE}/.robot3_venv")"
-echo ROBOT3_VENV="${ROBOT3_VENV}" >> "${WORKSPACE}/env.properties"
+REQUIRED_PYTHON="3.7.0"
 
-set -exu
+### Common functions
 
-echo "Installing Python Requirements"
+# Allows for the comparison of two Python version strings
+ver_cmp()
+{
+    local IFS=.
+    # shellcheck disable=SC2206
+    local V1=($1) V2=($2) I
+    for ((I=0 ; I<${#V1[*]} || I<${#V2[*]} ; I++)) ; do
+        [[ ${V1[$I]:-0} -lt ${V2[$I]:-0} ]] && echo -1 && return
+        [[ ${V1[$I]:-0} -gt ${V2[$I]:-0} ]] && echo 1 && return
+    done
+    echo 0
+}
+# Checks if first version/string is greater than or equal to the second
+ver_ge()
+{
+    [[ ! $(ver_cmp "$1" "$2") -eq -1 ]]
+}
+
+### Main script entry point
+
+# Check for required Python versions and activate/warn appropriately
+# Use PYENV for selecting the latest python version, if available
+if [[ -d "/opt/pyenv" ]]; then
+    echo "Setup pyenv:"
+    export PYENV_ROOT="/opt/pyenv"
+    export PATH="$PYENV_ROOT/bin:$PATH"
+    pyenv versions
+    if command -v pyenv 1>/dev/null 2>&1; then
+        eval "$(pyenv init - --no-rehash)"
+        # Choose the latest numeric Python version from installed list
+        version=$(pyenv versions --bare | sed '/^[^0-9]/d' |\
+            sort -V | tail -n 1)
+        pyenv local "${version}"
+    fi
+fi
+
+# Store the active/current Python3 version
+PYTHON_VERSION=$(python3 --version | awk '{print $2}')
+
+#  Check that the required minimum version has been met
+if ! (ver_ge "${PYTHON_VERSION}" "${REQUIRED_PYTHON}"); then
+    echo "Warning: possible Python version problem"
+    echo "Python ${PYTHON_VERSION} does not meet requirement: ${REQUIRED_PYTHON}"
+fi
+
+if (python3 -m robot.run --version > /dev/null 2>&1); then
+    echo "Working robot framework found; no installation necessary"
+    echo "Installed under Python version: ${PYTHON_VERSION}"
+    exit 0
+fi
+
+
+# Create a requirements file; keep it around for potential later use
+# Versions and dependencies below have been carefully tested for Python3
 cat << 'EOF' > "requirements.txt"
 paramiko
 six
@@ -79,6 +125,49 @@
 odltools
 EOF
 
-python3 -m pip install -r requirements.txt
+
+if [[ -f ~/lf-env.sh ]]; then
+    echo "Installing robot-framework using LF common tooling"
+    # shellcheck disable=SC1090
+    source ~/lf-env.sh
+
+    # Create a virtual environment for robot tests and make sure setuptools & wheel
+    # are up-to-date in addition to pip
+    lf-activate-venv --python python3 --venv-file "${WORKSPACE}/.robot3_venv" \
+    setuptools \
+    pip \
+    wheel
+
+    # Install the robot framework and other dependencies
+    python3 -m pip install -r requirements.txt
+
+    # Save the virtual environment in ROBOT3_VENV
+    ROBOT3_VENV="$(cat "${WORKSPACE}/.robot3_venv")"
+
+else
+    echo "Installing robot-framework in a virtual environment"
+    if [[ -z "${WORKSPACE}" ]]; then
+        # Use a temporary folder location
+        WORKSPACE="/tmp"
+        ROBOT3_VENV=$(mktemp -d --suffix=-robot3_venv)
+    else
+        ROBOT3_VENV="${WORKSPACE}/.robot3_venv"
+    fi
+
+    # The --system-site-packages parameter allows us to pick up system level
+    # installed packages. This allows us to bake matplotlib which takes very long
+    # to install into the image.
+    python3 -m venv --system-site-packages "${ROBOT3_VENV}"
+    source "${ROBOT3_VENV}/bin/activate"
+
+    echo "Installing robot-framework using basic methods"
+    python3 -m pip install -r requirements.txt
+fi
+
+# Store the virtual environment location
+echo "ROBOT3_VENV=${ROBOT3_VENV}" >> "${WORKSPACE}/env.properties"
+
+# Display versioning/debugging output
+python3 --version
 python3 -m pip freeze
 python3 -m robot.run --version || :
diff --git a/jjb/integration/prepare-csit.sh b/jjb/integration/prepare-csit.sh
index 1052bff..0db140f 100644
--- a/jjb/integration/prepare-csit.sh
+++ b/jjb/integration/prepare-csit.sh
@@ -24,18 +24,48 @@
 
 ROBOT_INSTALLER='include-raw-integration-install-robotframework-py3.sh'
 
-if !(which git > /dev/null 2>&1); then
-    echo "GIT binary not found current PATH"
-    echo $PATH; exit 1
+# Allows testing for root permissions
+REQ_USER=$(id -un)
+
+if ! (which git > /dev/null 2>&1); then
+    echo "GIT binary not found in current PATH"
+    # Add missing package to prevent script/job failures
+    if (grep Ubuntu /etc/os-release > /dev/null 2>&1) || \
+    (grep Debian /etc/os-release > /dev/null 2>&1); then
+        echo "Installing package dependency for Ubuntu/Debian"
+        if [[ "${REQ_USER}" == 'root' ]]; then
+            apt-get update
+            apt-get install -y git
+        else
+            sudo apt-get update
+            sudo apt-get install -y git
+        fi
+    elif (grep RedHat /etc/os-release > /dev/null 2>&1) || \
+    (grep CentOS /etc/os-release > /dev/null 2>&1); then
+        echo "Installing package dependency for CentOS/RedHat"
+        if [[ "${REQ_USER}" == 'root' ]]; then
+            yum install -y git
+        else
+            sudo yum install -y git
+        fi
+    else
+        echo "Warning: unmatched OS/distribution"
+        echo "Missing software will not be installed"
+    fi
 fi
 
-if [ -z "$WORKSPACE" ]; then
-    # shellcheck disable=SC2155
-    export WORKSPACE=`git rev-parse --show-toplevel`
+if [[ -z "${WORKSPACE}" ]]; then
+    if (git rev-parse --show-toplevel > /dev/null 2>&1); then
+        WORKSPACE=$(git rev-parse --show-toplevel)
+        export WORKSPACE
+    else
+        WORKSPACE=$(pwd)
+        export WORKSPACE
+    fi
 fi
 
 # shellcheck disable=SC2034
-TESTPLANDIR=${WORKSPACE}/${TESTPLAN}
+TESTPLANDIR="${WORKSPACE}/${TESTPLAN}"
 
 # Python version should match that used to setup
 #  robot-framework in other jobs/stages
@@ -56,22 +86,26 @@
 
 # Assume that if ROBOT3_VENV is set, virtualenv
 #  with system site packages can be activated
-if [ -f ${WORKSPACE}/env.properties ]; then
-    source ${WORKSPACE}/env.properties
+if [[ -f "${WORKSPACE}/env.properties" ]]; then
+    source "${WORKSPACE}/env.properties"
+elif [[ -f /tmp/env.properties ]]; then
+    source /tmp/env.properties
 fi
-if [ -f ${ROBOT3_VENV}/bin/activate ]; then
-    source ${ROBOT3_VENV}/bin/activate
+
+if [[ -f "${ROBOT3_VENV}/bin/activate" ]]; then
+    source "${ROBOT3_VENV}/bin/activate"
 else
     # Robot framework was not found
     #  clone ci-management repository and use install script
     git clone "https://gerrit.onap.org/r/ci-management" \
         /tmp/ci-management
-    source /tmp/ci-management/jjb/integration/${ROBOT_INSTALLER}
+    # shellcheck disable=SC1090
+    source "/tmp/ci-management/jjb/integration/${ROBOT_INSTALLER}"
 fi
 
 # install eteutils
-mkdir -p ${ROBOT3_VENV}/src/onap
-rm -rf ${ROBOT3_VENV}/src/onap/testsuite
+mkdir -p "${ROBOT3_VENV}/src/onap"
+rm -rf "${ROBOT3_VENV}/src/onap/testsuite"
 # Source from the Nexus repository
 python3 -m pip install --upgrade \
     --extra-index-url="https://nexus3.onap.org/repository/PyPi.staging/simple" \
diff --git a/jjb/integration/run-csit.sh b/jjb/integration/run-csit.sh
index bd35ac4..877cebb 100644
--- a/jjb/integration/run-csit.sh
+++ b/jjb/integration/run-csit.sh
@@ -15,13 +15,35 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-# $1 project/functionality
-# $2 robot options
+# $1 project/functionality {TESTPLAN}
+# $2 robot options         {TESTOPTIONS}
 
 echo "---> run-csit.sh"
 
 WORKDIR=$(mktemp -d --suffix=-robot-workdir)
 
+# Exit if no arguments are provided and required variables not set
+if [[ $# -eq 0 ]] && [[ -z "${TESTPLAN}" ]] && [[ -z "${TESTOPTIONS}" ]]; then
+    echo
+    echo "Usage: $0 plans/<project>/<functionality> [<robot-options>]"
+    echo
+    echo " <project>, <functionality>, <robot-options>:  "
+    echo "  The same values as for the JJB job template:"
+    echo '  {project}-csit-{functionality}'
+    echo
+    exit 1
+
+elif [[ $# -ne 2 ]] && [[ -z "${TESTPLAN}" ]] && [[ -z "${TESTOPTIONS}" ]]; then
+    echo
+    echo "Script called without arguments, but the following variables"
+    echo " must be set: {TESTPLAN} {TESTOPTIONS}"
+    echo
+    exit 1
+
+elif [[ $# -eq 2 ]]; then
+    export TESTPLAN=$1; export TESTOPTIONS=$2
+fi
+
 # Python version should match that used to setup
 #  robot-framework in other jobs/stages
 # Use pyenv for selecting the python version
@@ -43,12 +65,52 @@
 # functions
 #
 
+# load the saved set options
+function load_set {
+    _setopts="$-"
+
+    # bash shellopts
+    for i in $(echo "$SHELLOPTS" | tr ':' ' ') ; do
+        set +o "${i}"
+    done
+    for i in $(echo "$RUN_CSIT_SHELLOPTS" | tr ':' ' ') ; do
+        set -o "${i}"
+    done
+
+    # other options
+    for i in $(echo "$_setopts" | sed 's/./& /g') ; do
+        set +"${i}"
+    done
+    set -"${RUN_CSIT_SAVE_SET}"
+}
+
+# set options for quick bailout when error
+function harden_set {
+    set -xeo pipefail
+    set +u # enabled it would probably fail too many often
+}
+
+# relax set options so the sourced file will not fail
+# the responsibility is shifted to the sourced file...
+function relax_set {
+    set +e
+    set +o pipefail
+}
+
+# save current set options
+function save_set {
+    RUN_CSIT_SAVE_SET="$-"
+    RUN_CSIT_SHELLOPTS="$SHELLOPTS"
+}
+
 # wrapper for sourcing a file
 function source_safely {
-    [ -z "$1" ] && return 1
-    relax_set
-    . "$1"
-    load_set
+    if [[ -z "$1" ]] && return 1; then
+        relax_set
+        # shellcheck disable=SC1090
+        source "$1"
+        load_set
+    fi
 }
 
 function on_exit {
@@ -66,11 +128,10 @@
     # Run teardown script plan if it exists
     cd "${TESTPLANDIR}"
     TEARDOWN="${TESTPLANDIR}/teardown.sh"
-    if [ -f "${TEARDOWN}" ]; then
+    if [[ -f "${TEARDOWN}" ]]; then
         echo "Running teardown script ${TEARDOWN}"
         source_safely "${TEARDOWN}"
     fi
-    # TODO: do something with the output
     exit $rc
 }
 # ensure that teardown and other finalizing steps are always executed
@@ -96,44 +157,6 @@
     echo
 }
 
-# save current set options
-function save_set {
-    RUN_CSIT_SAVE_SET="$-"
-    RUN_CSIT_SHELLOPTS="$SHELLOPTS"
-}
-
-# load the saved set options
-function load_set {
-    _setopts="$-"
-
-    # bash shellopts
-    for i in $(echo "$SHELLOPTS" | tr ':' ' ') ; do
-        set +o ${i}
-    done
-    for i in $(echo "$RUN_CSIT_SHELLOPTS" | tr ':' ' ') ; do
-        set -o ${i}
-    done
-
-    # other options
-    for i in $(echo "$_setopts" | sed 's/./& /g') ; do
-        set +${i}
-    done
-    set -${RUN_CSIT_SAVE_SET}
-}
-
-# set options for quick bailout when error
-function harden_set {
-    set -xeo pipefail
-    set +u # enabled it would probably fail too many often
-}
-
-# relax set options so the sourced file will not fail
-# the responsibility is shifted to the sourced file...
-function relax_set {
-    set +e
-    set +o pipefail
-}
-
 #
 # main
 #
@@ -141,29 +164,21 @@
 # set and save options for quick failure
 harden_set && save_set
 
-if [ $# -eq 0 ]; then
-    echo
-    echo "Usage: $0 plans/<project>/<functionality> [<robot-options>]"
-    echo
-    echo "    <project>, <functionality>, <robot-options>:  "
-    echo "        The same values as for the '{project}-csit-{functionality}' JJB job template."
-    echo
-    exit 1
+if [[ -z "${WORKSPACE}" ]]; then
+    if (git rev-parse --show-toplevel > /dev/null 2>&1); then
+        WORKSPACE=$(git rev-parse --show-toplevel)
+        export WORKSPACE
+    else
+        WORKSPACE=$(pwd)
+        export WORKSPACE
+    fi
 fi
 
-if [ -z "$WORKSPACE" ]; then
-    export WORKSPACE=$(git rev-parse --show-toplevel)
-fi
-
-if [ -f "${WORKSPACE}/${1}/testplan.txt" ]; then
-    export TESTPLAN="${1}"
-else
+if [[ ! -f "${WORKSPACE}/${TESTPLAN}/testplan.txt" ]]; then
     echo "testplan not found: ${WORKSPACE}/${TESTPLAN}/testplan.txt"
     exit 2
 fi
 
-export TESTOPTIONS="${2}"
-
 rm -rf "$WORKSPACE/archives/$TESTPLAN"
 mkdir -p "$WORKSPACE/archives/$TESTPLAN"
 
@@ -178,7 +193,7 @@
 cd "${WORKDIR}"
 
 # Add csit scripts to PATH
-export PATH="${PATH}:${WORKSPACE}/docker/scripts:${WORKSPACE}/scripts:${ROBOT_VENV}/bin"
+export PATH="${PATH}:${WORKSPACE}/docker/scripts:${WORKSPACE}/scripts:${ROBOT3_VENV}/bin"
 export SCRIPTS="${WORKSPACE}/scripts"
 export ROBOT_VARIABLES=
 
@@ -199,7 +214,8 @@
 # Run test plan
 cd "$WORKDIR"
 echo "Reading the testplan:"
-cat "${TESTPLANDIR}/testplan.txt" | egrep -v '(^[[:space:]]*#|^[[:space:]]*$)' | sed "s|^|${WORKSPACE}/tests/|" > testplan.txt
+grep -E -v '(^[[:space:]]*#|^[[:space:]]*$)' "${TESTPLANDIR}/testplan.txt" |\
+    sed "s|^|${WORKSPACE}/tests/|" > testplan.txt
 cat testplan.txt
 SUITES=$( xargs -a testplan.txt )
 
@@ -212,10 +228,9 @@
 pip freeze
 python3 -m robot.run --version || :
 
-python -m robot.run -N ${TESTPLAN} -v WORKSPACE:/tmp ${ROBOT_VARIABLES} ${TESTOPTIONS} ${SUITES}
+python -m robot.run -N "${TESTPLAN}" -v WORKSPACE:/tmp "${ROBOT_VARIABLES}" "${TESTOPTIONS}" "${SUITES}"
 RESULT=$?
 load_set
 echo "RESULT: $RESULT"
 # Note that the final steps are done in on_exit function after this exit!
 exit $RESULT
-