X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=resources%2Flibraries%2Fbash%2Ffunction%2Fcommon.sh;h=e576f99d8d322bc8de879e452872761e7e4f0e2f;hb=c79c748013ec53ff59e9260034822a5b26afa441;hp=96c7940f7973ae148ddea7fc31d2e2d8c94d317b;hpb=755842654e0d7144de9fafb836fedc6ebe794c95;p=csit.git diff --git a/resources/libraries/bash/function/common.sh b/resources/libraries/bash/function/common.sh index 96c7940f79..e576f99d8d 100644 --- a/resources/libraries/bash/function/common.sh +++ b/resources/libraries/bash/function/common.sh @@ -23,7 +23,6 @@ set -exuo pipefail function activate_docker_topology () { - set -exuo pipefail # Create virtual vpp-device topology. Output of the function is topology # file describing created environment saved to a file. @@ -34,9 +33,12 @@ function activate_docker_topology () { # - NODENESS - Node multiplicity of desired testbed. # - FLAVOR - Node flavor string, usually describing the processor. # - IMAGE_VER_FILE - Name of file that contains the image version. + # - CSIT_DIR - Directory where ${IMAGE_VER_FILE} is located. # Variables set: # - WORKING_TOPOLOGY - Path to topology file. + set -exuo pipefail + source "${BASH_FUNCTION_DIR}/device.sh" || { die "Source failed!" } @@ -44,11 +46,11 @@ function activate_docker_topology () { device_image="$(< ${CSIT_DIR}/${IMAGE_VER_FILE})" case_text="${NODENESS}_${FLAVOR}" case "${case_text}" in - "1n_skx") + "1n_skx" | "1n_tx2") # We execute reservation over csit-shim-dcr (ssh) which runs sourced # script's functions. Env variables are read from ssh output # back to localhost for further processing. - hostname=$(grep search /etc/resolv.conf | cut -d' ' -f3) + hostname=$(grep search /etc/resolv.conf | cut -d' ' -f3) || die ssh="ssh root@${hostname} -p 6022" run="activate_wrapper ${NODENESS} ${FLAVOR} ${device_image}" # backtics to avoid https://midnight-commander.org/ticket/2142 @@ -91,8 +93,6 @@ function activate_docker_topology () { function activate_virtualenv () { - set -exuo pipefail - # Update virtualenv pip package, delete and create virtualenv directory, # activate the virtualenv, install requirements, set PYTHONPATH. @@ -107,6 +107,8 @@ function activate_virtualenv () { # Functions called: # - die - Print to stderr and exit. + set -exuo pipefail + root_path="${1-$CSIT_DIR}" env_dir="${root_path}/env" req_path=${2-$CSIT_DIR/requirements.txt} @@ -130,8 +132,6 @@ function activate_virtualenv () { function archive_tests () { - set -exuo pipefail - # Create .tar.xz of generated/tests for archiving. # To be run after generate_tests, kept separate to offer more flexibility. @@ -140,6 +140,8 @@ function archive_tests () { # File rewriten: # - ${ARCHIVE_DIR}/tests.tar.xz - Archive of generated tests. + set -exuo pipefail + tar c "${GENERATED_DIR}/tests" | xz -9e > "${ARCHIVE_DIR}/tests.tar.xz" || { die "Error creating archive of generated tests." } @@ -148,8 +150,6 @@ function archive_tests () { function check_download_dir () { - set -exuo pipefail - # Fail if there are no files visible in ${DOWNLOAD_DIR}. # # Variables read: @@ -159,6 +159,8 @@ function check_download_dir () { # Functions called: # - die - Print to stderr and exit. + set -exuo pipefail + if [[ ! "$(ls -A "${DOWNLOAD_DIR}")" ]]; then die "No artifacts downloaded!" fi @@ -167,12 +169,12 @@ function check_download_dir () { function cleanup_topo () { - set -exuo pipefail - # Variables read: # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed. # - PYTHON_SCRIPTS_DIR - Path to directory holding the reservation script. + set -exuo pipefail + python "${PYTHON_SCRIPTS_DIR}/topo_cleanup.py" -t "${WORKING_TOPOLOGY}" # Not using "|| die" as some callers might want to ignore errors, # e.g. in teardowns, such as unreserve. @@ -181,8 +183,6 @@ function cleanup_topo () { function common_dirs () { - set -exuo pipefail - # Set global variables, create some directories (without touching content). # Variables set: @@ -200,37 +200,43 @@ function common_dirs () { # Functions called: # - die - Print to stderr and exit. - BASH_FUNCTION_DIR="$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")" || { - die "Some error during localizing this source directory." + set -exuo pipefail + + this_file=$(readlink -e "${BASH_SOURCE[0]}") || { + die "Some error during locating of this source file." + } + BASH_FUNCTION_DIR=$(dirname "${this_file}") || { + die "Some error during dirname call." } # Current working directory could be in a different repo, e.g. VPP. pushd "${BASH_FUNCTION_DIR}" || die "Pushd failed" - CSIT_DIR="$(readlink -e "$(git rev-parse --show-toplevel)")" || { - die "Readlink or git rev-parse failed." + relative_csit_dir=$(git rev-parse --show-toplevel) || { + die "Git rev-parse failed." } + CSIT_DIR=$(readlink -e "${relative_csit_dir}") || die "Readlink failed." popd || die "Popd failed." - TOPOLOGIES_DIR="$(readlink -e "${CSIT_DIR}/topologies/available")" || { + TOPOLOGIES_DIR=$(readlink -e "${CSIT_DIR}/topologies/available") || { die "Readlink failed." } - RESOURCES_DIR="$(readlink -e "${CSIT_DIR}/resources")" || { + RESOURCES_DIR=$(readlink -e "${CSIT_DIR}/resources") || { die "Readlink failed." } - TOOLS_DIR="$(readlink -e "${RESOURCES_DIR}/tools")" || { + TOOLS_DIR=$(readlink -e "${RESOURCES_DIR}/tools") || { die "Readlink failed." } - PYTHON_SCRIPTS_DIR="$(readlink -e "${TOOLS_DIR}/scripts")" || { + PYTHON_SCRIPTS_DIR=$(readlink -e "${TOOLS_DIR}/scripts") || { die "Readlink failed." } - ARCHIVE_DIR="$(readlink -f "${CSIT_DIR}/archive")" || { + ARCHIVE_DIR=$(readlink -f "${CSIT_DIR}/archive") || { die "Readlink failed." } mkdir -p "${ARCHIVE_DIR}" || die "Mkdir failed." - DOWNLOAD_DIR="$(readlink -f "${CSIT_DIR}/download_dir")" || { + DOWNLOAD_DIR=$(readlink -f "${CSIT_DIR}/download_dir") || { die "Readlink failed." } mkdir -p "${DOWNLOAD_DIR}" || die "Mkdir failed." - GENERATED_DIR="$(readlink -f "${CSIT_DIR}/generated")" || { + GENERATED_DIR=$(readlink -f "${CSIT_DIR}/generated") || { die "Readlink failed." } mkdir -p "${GENERATED_DIR}" || die "Mkdir failed." @@ -239,8 +245,6 @@ function common_dirs () { function compose_pybot_arguments () { - set -exuo pipefail - # Variables read: # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed. # - DUT - CSIT test/ subdirectory, set while processing tags. @@ -251,6 +255,8 @@ function compose_pybot_arguments () { # - PYBOT_ARGS - String holding part of all arguments for pybot. # - EXPANDED_TAGS - Array of strings pybot arguments compiled from tags. + set -exuo pipefail + # No explicit check needed with "set -u". PYBOT_ARGS=("--loglevel" "TRACE") PYBOT_ARGS+=("--variable" "TOPOLOGY_PATH:${WORKING_TOPOLOGY}") @@ -282,8 +288,10 @@ function compose_pybot_arguments () { function copy_archives () { - set -exuo pipefail - + # Create additional archive if workspace variable is set. + # This way if script is running in jenkins all will be + # automatically archived to logs.fd.io. + # # Variables read: # - WORKSPACE - Jenkins workspace, copy only if the value is not empty. # Can be unset, then it speeds up manual testing. @@ -294,9 +302,8 @@ function copy_archives () { # Functions called: # - die - Print to stderr and exit. - # We will create additional archive if workspace variable is set. - # This way if script is running in jenkins all will be - # automatically archived to logs.fd.io. + set -exuo pipefail + if [[ -n "${WORKSPACE-}" ]]; then mkdir -p "${WORKSPACE}/archives/" || die "Archives dir create failed." cp -rf "${ARCHIVE_DIR}"/* "${WORKSPACE}/archives" || die "Copy failed." @@ -305,6 +312,7 @@ function copy_archives () { function deactivate_docker_topology () { + # Deactivate virtual vpp-device topology by removing containers. # # Variables read: @@ -315,10 +323,10 @@ function deactivate_docker_topology () { case_text="${NODENESS}_${FLAVOR}" case "${case_text}" in - "1n_skx") - hostname=$(grep search /etc/resolv.conf | cut -d' ' -f3) + "1n_skx" | "1n_tx2") + hostname=$(grep search /etc/resolv.conf | cut -d' ' -f3) || die ssh="ssh root@${hostname} -p 6022" - env_vars="$(env | grep CSIT_ | tr '\n' ' ' )" + env_vars=$(env | grep CSIT_ | tr '\n' ' ' ) || die ${ssh} "$(declare -f); deactivate_wrapper ${env_vars}" || { die "Topology cleanup via shim-dcr failed!" } @@ -337,6 +345,7 @@ function deactivate_docker_topology () { function die () { + # Print the message to standard error end exit with error code specified # by the second argument. # @@ -355,8 +364,6 @@ function die () { function die_on_pybot_error () { - set -exuo pipefail - # Source this fragment if you want to abort on any failed test case. # # Variables read: @@ -364,6 +371,8 @@ function die_on_pybot_error () { # Functions called: # - die - Print to stderr and exit. + set -exuo pipefail + if [[ "${PYBOT_EXIT_STATUS}" != "0" ]]; then die "Test failures are present!" "${PYBOT_EXIT_STATUS}" fi @@ -372,8 +381,6 @@ function die_on_pybot_error () { function generate_tests () { - set -exuo pipefail - # Populate ${GENERATED_DIR}/tests based on ${CSIT_DIR}/tests/. # Any previously existing content of ${GENERATED_DIR}/tests is wiped before. # The generation is done by executing any *.py executable @@ -387,8 +394,10 @@ function generate_tests () { # Directories replaced: # - ${GENERATED_DIR}/tests - Overwritten by the generated tests. - rm -rf "${GENERATED_DIR}/tests" - cp -r "${CSIT_DIR}/tests" "${GENERATED_DIR}/tests" + set -exuo pipefail + + rm -rf "${GENERATED_DIR}/tests" || die + cp -r "${CSIT_DIR}/tests" "${GENERATED_DIR}/tests" || die cmd_line=("find" "${GENERATED_DIR}/tests" "-type" "f") cmd_line+=("-executable" "-name" "*.py") file_list=$("${cmd_line[@]}") || die @@ -405,8 +414,6 @@ function generate_tests () { function get_test_code () { - set -exuo pipefail - # Arguments: # - ${1} - Optional, argument of entry script (or empty as unset). # Test code value to override job name from environment. @@ -417,6 +424,8 @@ function get_test_code () { # - NODENESS - Node multiplicity of desired testbed. # - FLAVOR - Node flavor string, usually describing the processor. + set -exuo pipefail + TEST_CODE="${1-}" || die "Reading optional argument failed, somehow." if [[ -z "${TEST_CODE}" ]]; then TEST_CODE="${JOB_NAME-}" || die "Reading job name failed, somehow." @@ -431,6 +440,10 @@ function get_test_code () { NODENESS="1n" FLAVOR="skx" ;; + *"1n-tx2"*) + NODENESS="1n" + FLAVOR="tx2" + ;; *"2n-skx"*) NODENESS="2n" FLAVOR="skx" @@ -462,18 +475,18 @@ function get_test_code () { function get_test_tag_string () { - set -exuo pipefail - # Variables read: # - GERRIT_EVENT_TYPE - Event type set by gerrit, can be unset. # - GERRIT_EVENT_COMMENT_TEXT - Comment text, read for "comment-added" type. # - TEST_CODE - The test selection string from environment or argument. # Variables set: - # - TEST_TAG_STRING - The string following "perftest" in gerrit comment, - # or empty. + # - TEST_TAG_STRING - The string following trigger word in gerrit comment. + # May be empty, not set on event types not adding comment. # TODO: ci-management scripts no longer need to perform this. + set -exuo pipefail + trigger="" if [[ "${GERRIT_EVENT_TYPE-}" == "comment-added" ]]; then case "${TEST_CODE}" in @@ -509,8 +522,6 @@ function get_test_tag_string () { function reserve_and_cleanup_testbed () { - set -exuo pipefail - # Reserve physical testbed, perform cleanup, register trap to unreserve. # When cleanup fails, remove from topologies and keep retrying # until all topologies are removed. @@ -530,6 +541,8 @@ function reserve_and_cleanup_testbed () { # Traps registered: # - EXIT - Calls cancel_all for ${WORKING_TOPOLOGY}. + set -exuo pipefail + while [[ ${TOPOLOGIES[@]} ]]; do for topo in "${TOPOLOGIES[@]}"; do set +e @@ -600,8 +613,8 @@ function reserve_and_cleanup_testbed () { function run_pybot () { - set -exuo pipefail - + # Run pybot with options based on input variables. Create output_info.xml + # # Variables read: # - CSIT_DIR - Path to existing root of local CSIT git repository. # - ARCHIVE_DIR - Path to store robot result files in. @@ -612,6 +625,8 @@ function run_pybot () { # Functions called: # - die - Print to stderr and exit. + set -exuo pipefail + all_options=("--outputdir" "${ARCHIVE_DIR}" "${PYBOT_ARGS[@]}") all_options+=("--noncritical" "EXPECTED_FAILING") all_options+=("${EXPANDED_TAGS[@]}") @@ -628,24 +643,73 @@ function run_pybot () { all_options+=("--report" "none") all_options+=("--output" "${ARCHIVE_DIR}/output_info.xml") all_options+=("${ARCHIVE_DIR}/output.xml") - rebot "${all_options[@]}" + rebot "${all_options[@]}" || true popd || die "Change directory operation failed." } -function select_tags () { +function select_arch_os () { + + # Populate variables related to local operating system. + # + # Also install any missing prerequisities CSIT tests need. + # TODO: Move the installation to a separate function? + # + # Variables set: + # - VPP_VER_FILE - Name of File in CSIT dir containing vpp stable version. + # - IMAGE_VER_FILE - Name of File in CSIT dir containing the image name. + # - PKG_SUFFIX - Suffix of OS package file name, "rpm" or "deb." set -exuo pipefail + os_id=$(grep '^ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') || { + die "Get OS release failed." + } + + case "${os_id}" in + "ubuntu"*) + IMAGE_VER_FILE="VPP_DEVICE_IMAGE_UBUNTU" + VPP_VER_FILE="VPP_STABLE_VER_UBUNTU_BIONIC" + PKG_SUFFIX="deb" + ;; + "centos"*) + IMAGE_VER_FILE="VPP_DEVICE_IMAGE_CENTOS" + VPP_VER_FILE="VPP_STABLE_VER_CENTOS" + PKG_SUFFIX="rpm" + ;; + *) + die "Unable to identify distro or os from ${os_id}" + ;; + esac + + arch=$(uname -m) || { + die "Get CPU architecture failed." + } + + case "${arch}" in + "aarch64") + IMAGE_VER_FILE="${IMAGE_VER_FILE}_ARM" + ;; + *) + ;; + esac +} + + +function select_tags () { + # Variables read: # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed. # - TEST_CODE - String affecting test selection, usually jenkins job name. # - TEST_TAG_STRING - String selecting tags, from gerrit comment. # Can be unset. # - TOPOLOGIES_DIR - Path to existing directory with available tpologies. + # - BASH_FUNCTION_DIR - Directory with input files to process. # Variables set: # - TAGS - Array of processed tag boolean expressions. + set -exuo pipefail + # NIC SELECTION start_pattern='^ TG:' end_pattern='^ \? \?[A-Za-z0-9]\+:' @@ -658,40 +722,46 @@ function select_tags () { reserved=$(sed "${sed_command}" "${WORKING_TOPOLOGY}" \ | grep -hoP "model: \K.*" | sort -u) # All topologies DUT NICs - Selected topology DUT NICs - exclude_nics=($(comm -13 <(echo "${reserved}") <(echo "${available}"))) + exclude_nics=($(comm -13 <(echo "${reserved}") <(echo "${available}"))) || { + die "Computation of excluded NICs failed." + } - # Select default NIC + # Select default NIC tag. case "${TEST_CODE}" in *"3n-dnv"* | *"2n-dnv"*) - DEFAULT_NIC='nic_intel-x553' + default_nic="nic_intel-x553" ;; *"3n-tsh"*) - DEFAULT_NIC='nic_intel-x520-da2' + default_nic="nic_intel-x520-da2" ;; *) - DEFAULT_NIC='nic_intel-x710' + default_nic="nic_intel-x710" ;; esac + sed_nic_sub_cmd="sed s/\${default_nic}/${default_nic}/" + # Tag file directory shorthand. + tfd="${BASH_FUNCTION_DIR}" case "${TEST_CODE}" in # Select specific performance tests based on jenkins job type variable. *"ndrpdr-weekly"* ) - readarray -t test_tag_array < "${BASH_FUNCTION_DIR}/mlr-weekly.txt" + readarray -t test_tag_array < "${tfd}/mlr-weekly.txt" || die ;; *"mrr-daily"* ) - readarray -t test_tag_array < "${BASH_FUNCTION_DIR}/mrr-daily.txt" + readarray -t test_tag_array <<< $(${sed_nic_sub_cmd} \ + ${tfd}/mrr-daily-${FLAVOR}.txt) || die ;; *"mrr-weekly"* ) - readarray -t test_tag_array < "${BASH_FUNCTION_DIR}/mrr-weekly.txt" + readarray -t test_tag_array < "${tfd}/mrr-weekly.txt" || die ;; * ) if [[ -z "${TEST_TAG_STRING-}" ]]; then # If nothing is specified, we will run pre-selected tests by # following tags. - test_tag_array=("mrrAND${DEFAULT_NIC}AND1cAND64bANDip4base" - "mrrAND${DEFAULT_NIC}AND1cAND78bANDip6base" - "mrrAND${DEFAULT_NIC}AND1cAND64bANDl2bdbase" - "mrrAND${DEFAULT_NIC}AND1cAND64bANDl2xcbase" + test_tag_array=("mrrAND${default_nic}AND1cAND64bANDip4base" + "mrrAND${default_nic}AND1cAND78bANDip6base" + "mrrAND${default_nic}AND1cAND64bANDl2bdbase" + "mrrAND${default_nic}AND1cAND64bANDl2xcbase" "!dot1q" "!drv_avf") else # If trigger contains tags, split them into array. @@ -701,6 +771,10 @@ function select_tags () { esac # Blacklisting certain tags per topology. + # + # Reasons for blacklisting: + # - ipsechw - Blacklisted on testbeds without crypto hardware accelerator. + # TODO: Add missing reasons here (if general) or where used (if specific). case "${TEST_CODE}" in *"2n-skx"*) test_tag_array+=("!ipsechw") @@ -716,12 +790,14 @@ function select_tags () { test_tag_array+=("!srv6_proxy") test_tag_array+=("!vhost") test_tag_array+=("!vts") + test_tag_array+=("!drv_avf") ;; *"3n-dnv"*) test_tag_array+=("!memif") test_tag_array+=("!srv6_proxy") test_tag_array+=("!vhost") test_tag_array+=("!vts") + test_tag_array+=("!drv_avf") ;; *"3n-tsh"*) test_tag_array+=("!ipsechw") @@ -763,7 +839,7 @@ function select_tags () { if [[ "${TEST_TAG_STRING-}" == *"nic_"* ]]; then prefix="${prefix}mrrAND" else - prefix="${prefix}mrrAND${DEFAULT_NIC}AND" + prefix="${prefix}mrrAND${default_nic}AND" fi fi for tag in "${test_tag_array[@]}"; do @@ -780,86 +856,8 @@ function select_tags () { } -function select_vpp_device_tags () { - - set -exuo pipefail - - # Variables read: - # - TEST_CODE - String affecting test selection, usually jenkins job name. - # - TEST_TAG_STRING - String selecting tags, from gerrit comment. - # Can be unset. - # Variables set: - # - TAGS - Array of processed tag boolean expressions. - - case "${TEST_CODE}" in - # Select specific performance tests based on jenkins job type variable. - * ) - if [[ -z "${TEST_TAG_STRING-}" ]]; then - # If nothing is specified, we will run pre-selected tests by - # following tags. Items of array will be concatenated by OR - # in Robot Framework. - test_tag_array=() - else - # If trigger contains tags, split them into array. - test_tag_array=(${TEST_TAG_STRING//:/ }) - fi - ;; - esac - - TAGS=() - - # We will prefix with perftest to prevent running other tests - # (e.g. Functional). - prefix="devicetestAND" - if [[ "${TEST_CODE}" == "vpp-"* ]]; then - # Automatic prefixing for VPP jobs to limit testing. - prefix="${prefix}" - fi - for tag in "${test_tag_array[@]}"; do - if [[ ${tag} == "!"* ]]; then - # Exclude tags are not prefixed. - TAGS+=("${tag}") - else - TAGS+=("${prefix}${tag}") - fi - done -} - -function select_os () { - - set -exuo pipefail - - # Variables set: - # - VPP_VER_FILE - Name of File in CSIT dir containing vpp stable version. - # - IMAGE_VER_FILE - Name of File in CSIT dir containing the image name. - # - PKG_SUFFIX - Suffix of OS package file name, "rpm" or "deb." - - os_id=$(grep '^ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') || { - die "Get OS release failed." - } - - case "${os_id}" in - "ubuntu"*) - IMAGE_VER_FILE="VPP_DEVICE_IMAGE_UBUNTU" - VPP_VER_FILE="VPP_STABLE_VER_UBUNTU_BIONIC" - PKG_SUFFIX="deb" - ;; - "centos"*) - IMAGE_VER_FILE="VPP_DEVICE_IMAGE_CENTOS" - VPP_VER_FILE="VPP_STABLE_VER_CENTOS" - PKG_SUFFIX="rpm" - ;; - *) - die "Unable to identify distro or os from ${OS}" - ;; - esac -} - - function select_topology () { - set -exuo pipefail - # Variables read: # - NODENESS - Node multiplicity of testbed, either "2n" or "3n". # - FLAVOR - Node flavor string, currently either "hsw" or "skx". @@ -871,14 +869,17 @@ function select_topology () { # Functions called: # - die - Print to stderr and exit. + set -exuo pipefail + case_text="${NODENESS}_${FLAVOR}" case "${case_text}" in # TODO: Move tags to "# Blacklisting certain tags per topology" section. + # TODO: Double link availability depends on NIC used. "1n_vbox") TOPOLOGIES=( "${TOPOLOGIES_DIR}"/*vpp_device*.template ) TOPOLOGIES_TAGS="2_node_single_link_topo" ;; - "1n_skx") + "1n_skx" | "1n_tx2") TOPOLOGIES=( "${TOPOLOGIES_DIR}"/*vpp_device*.template ) TOPOLOGIES_TAGS="2_node_single_link_topo" ;; @@ -918,7 +919,53 @@ function select_topology () { } +function select_vpp_device_tags () { + + # Variables read: + # - TEST_CODE - String affecting test selection, usually jenkins job name. + # - TEST_TAG_STRING - String selecting tags, from gerrit comment. + # Can be unset. + # Variables set: + # - TAGS - Array of processed tag boolean expressions. + + set -exuo pipefail + + case "${TEST_CODE}" in + # Select specific performance tests based on jenkins job type variable. + * ) + if [[ -z "${TEST_TAG_STRING-}" ]]; then + # If nothing is specified, we will run pre-selected tests by + # following tags. Items of array will be concatenated by OR + # in Robot Framework. + test_tag_array=() + else + # If trigger contains tags, split them into array. + test_tag_array=(${TEST_TAG_STRING//:/ }) + fi + ;; + esac + + TAGS=() + + # We will prefix with devicetest to prevent running other tests + # (e.g. Functional). + prefix="devicetestAND" + if [[ "${TEST_CODE}" == "vpp-"* ]]; then + # Automatic prefixing for VPP jobs to limit testing. + prefix="${prefix}" + fi + for tag in "${test_tag_array[@]}"; do + if [[ ${tag} == "!"* ]]; then + # Exclude tags are not prefixed. + TAGS+=("${tag}") + else + TAGS+=("${prefix}${tag}") + fi + done +} + function untrap_and_unreserve_testbed () { + # Use this as a trap function to ensure testbed does not remain reserved. # Perhaps call directly before script exit, to free testbed for other jobs. # This function is smart enough to avoid multiple unreservations (so safe). @@ -957,10 +1004,13 @@ function untrap_and_unreserve_testbed () { function warn () { + # Print the message to standard error. # # Arguments: # - ${@} - The text of the message. + set -exuo pipefail + echo "$@" >&2 }