# Copyright (c) 2021 The Flatcar Maintainers. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # Vendor test helper script. Sourced by vendor tests. Does some # initial setup. # # # The initial setup consist of creating the vendor working directory # for the vendor test script, specifying the variables described below # and changing the current working directory to the vendor working # directory. # # # The vendor test script is expected to keep all artifacts it produces # in its current working directory. # # # The script specifies the following variables for the vendor test # script to use: # # CIA_VERNUM: # Image version. In case of developer builds it comes with a suffix, # so it looks like "3217.0.0+nightly-20220422-0155". For release # builds the version will be without suffix, so it looks like # "3217.0.0". Whether the build is a release or a developer one is # reflected in CIA_BUILD_TYPE variable described below. # # CIA_ARCH: # Architecture to test. Currently it is either "amd64" or "arm64". # # CIA_TAPFILE: # Where the TAP reports should be written. Usually just passed to # kola throught the --tapfile parameter. # # CIA_CHANNEL: # A channel. Either "alpha", "beta", "stable" or "lts". Used to find # the last release for the update check. # # CIA_TESTSCRIPT: # Name of the vendor script. May be useful in some messages. # # CIA_GIT_VERSION: # The most recent tag for the current commit. # # CIA_BUILD_TYPE: # It's either "release" or "developer", based on the CIA_VERNUM # variable. # # CIA_FIRST_RUN: # 1 if this is a first run, 0 if it is a rerun of failed tests. # # # After this script is sourced, the parameters in ${@} specify test # cases / test case patterns to run. # "ciavts" stands for Continuous Integration Automation Vendor Test # Setup. This prefix is used to easily unset all the variables with # this prefix before leaving this file. ciavts_main_work_dir="${1}"; shift ciavts_work_dir="${1}"; shift ciavts_arch="${1}"; shift ciavts_vernum="${1}"; shift ciavts_tapfile="${1}"; shift # $@ now contains tests / test patterns to run source ci-automation/ci_automation_common.sh mkdir -p "${ciavts_work_dir}" ciavts_testscript=$(basename "${0}") ciavts_git_version=$(cat "${ciavts_main_work_dir}/git_version") ciavts_channel=$(cat "${ciavts_main_work_dir}/git_channel") if [[ "${ciavts_channel}" = 'developer' ]]; then ciavts_channel='alpha' fi # If vernum is like 3200.0.0+whatever, it's a developer build, # otherwise it's a release build. ciavts_type='developer' if [[ "${ciavts_vernum%%+*}" = "${ciavts_vernum}" ]]; then ciavts_type='release' fi # Make these paths absolute to avoid problems when changing # directories. ciavts_tapfile="${PWD}/${ciavts_work_dir}/${ciavts_tapfile}" ciavts_first_run=0 if [[ -f "${ciavts_main_work_dir}/first_run" ]]; then ciavts_first_run=1 fi echo "++++ Running ${ciavts_testscript} inside ${ciavts_work_dir} ++++" cd "${ciavts_work_dir}" CIA_VERNUM="${ciavts_vernum}" CIA_ARCH="${ciavts_arch}" CIA_TAPFILE="${ciavts_tapfile}" CIA_CHANNEL="${ciavts_channel}" CIA_TESTSCRIPT="${ciavts_testscript}" CIA_GIT_VERSION="${ciavts_git_version}" CIA_BUILD_TYPE="${ciavts_type}" CIA_FIRST_RUN="${ciavts_first_run}" # Unset all variables with ciavts_ prefix now. unset -v "${!ciavts_@}" # Prefixes all test names in the tap file with a given prefix, so the # test name like "cl.basic" will become "extra-test.[${prefix}].cl.basic". # # Typical use: # prefix_tap_file "${instance_type}" "${tapfile}" # # Parameters: # 1 - prefix # 2 - tap file, modified in place function prefix_tap_file() { local prefix="${1}"; shift local tap_file="${1}"; shift # drop the dots from prefix local actual_prefix="extra-test.[${prefix}]." sed --in-place --expression 's/^\(\s*\(not\)\?\s*ok[^-]*\s*-\s*\)\(\S\)/\1'"${actual_prefix}"'\3/g' "${tap_file}" } # Filters the test names, so it puts only the real names of the # prefixed tests into the chosen variable. For example for prefix # "foo", it will ignore the test name like "cl.basic", but will print # "cl.internet" for a test name like "extra-test.[foo].cl.internet". # "*" is treated specially - it will be inserted into the chosen # variable if it is passed. # # Typical use: # filter_prefixed_tests tests_to_run "${instance_type}" "${@}" # if [[ "${#tests_to_run[@]}" -gt 0 ]]; then …; fi # # Parameters: # 1 - name of an array variable where the filtering results will be stored # 2 - prefix # @ - test names function filter_prefixed_tests() { local var_name="${1}"; shift local prefix="${1}"; shift # rest of the parameters are test names local -n results="${var_name}" local name local stripped_name # clear the array, so it will contain results of current filtering # only results=() for name; do stripped_name="${name#extra-test.\[${prefix}\].}" if [[ "${stripped_name}" != "${name}" ]]; then results+=( "${stripped_name}" ) continue elif [[ "${name}" = '*' ]]; then results+=( '*' ) fi done } # Filters out the extra tests from the passed test names. Ignored test # names begin with "extra-test.". The results of the filtering are # inserted into the chosen variable. # # Typical use: # filter_out_prefixed_tests tests_to_run "${@}" # if [[ "${#tests_to_run[@]}" -gt 0 ]]; then …; fi # # Parameters: # 1 - name of an array variable where the filtering results will be stored # @ - test names function filter_out_prefixed_tests() { local var_name="${1}"; shift local -n results="${var_name}" local name # clear the array, so it will contain results of current filtering # only results=() for name; do if [[ "${name#extra-test.}" = "${name}" ]]; then results+=( "${name}" ) fi done } # Merges into the first (main) tap file the contents of other tap # files. It is very simple - the function assumes that all the tap # files begin with a line like: # # 1..${number_of_tests} # # Other lines that are processed should begin like: # # (not)? ok - ${test_name} # # Any other lines are copied verbatim. # # The other tap files should already be preprocessed by # prefix_tap_file to avoid duplicated test names. # # Typical use: # merge_tap_files "${tap_file}" extra-validation-*.tap # rm -f extra-validation-*.tap # # Parameters: # 1 - main tap file # @ - other tap files function merge_tap_files() { local main_tap_file="${1}"; shift # rest of the parameters are other tap files local main_test_count=0 if [[ -f "${main_tap_file}" ]]; then main_test_count=$(head --lines=1 "${main_tap_file}" | grep --only-matching '[0-9]\+$') fi local other_test_count local other_tap_file local tmp_tap_file="${main_tap_file}.mtf.tmp" for other_tap_file; do if [[ ! -f "${other_tap_file}" ]]; then continue fi other_test_count=$(head --lines=1 "${other_tap_file}" | grep --only-matching '[0-9]\+$' || echo 0 ) ((main_test_count+=other_test_count)) done echo "1..${main_test_count}" >"${tmp_tap_file}" if [[ -f "${main_tap_file}" ]]; then tail --lines=+2 "${main_tap_file}" >>"${tmp_tap_file}" fi for other_tap_file; do if [[ ! -f "${other_tap_file}" ]]; then continue fi tail --lines=+2 "${other_tap_file}" >>"${tmp_tap_file}" done mv --force "${tmp_tap_file}" "${main_tap_file}" } # Runs or reruns the tests on the main instance and other # instances. Other instances usually run a subset of tests only. # # For this function to work, the caller needs to define two functions # beforehand: # # run_kola_tests that takes the following parameters: # 1 - instance type # 2 - tap file # @ - tests to run # # query_kola_tests that takes the following parameters: # 1 - instance type # @ - tests to run # This function should print the names of the tests to run. Every line # of the output should have one test name to run. Any other cruft in # the line will be ignored. # # Typical use: # function run_kola_tests() { # local instance_type="${1}"; shift # local tap_file="${1}"; shift # kola run … "${@}" # } # # function query_kola_tests() { # local instance_type="${1}"; shift # kola list … "${@}" # } # # args=( # "${main_instance}" # "${CIA_TAPFILE}" # "${CIA_FIRST_RUN}" # "${other_instance_types[@]}" # '--' # 'cl.internet' # '--' # "${tests_to_run[@]}" # ) # run_kola_tests_on_instances "${args[@]}" # # Parameters: # 1 - main instance type - there all the tests are being run # 2 - main tap file # 3 - if this is first run (1 if it is, 0 if it is a rerun) # @ - other instance types followed by double dash (--) followed by # test names for other instances to filter from the tests to be # run followed by double dash, followed by tests to be run or # rerun function run_kola_tests_on_instances() { local main_instance_type="${1}"; shift local main_tapfile="${1}"; shift local is_first_run="${1}"; shift local other_instance_types=() local other_tests=() local arg while [[ "${#}" -gt 0 ]]; do arg="${1}"; shift if [[ "${arg}" = '--' ]]; then break fi other_instance_types+=( "${arg}" ) done while [[ "${#}" -gt 0 ]]; do arg="${1}"; shift if [[ "${arg}" = '--' ]]; then break fi other_tests+=( "${arg}" ) done # rest of the parameters are tests to be run or rerun local instance_type local queried_tests local instance_tests=() local tests_on_instances_running=0 local other_tests_for_fgrep other_tests_for_fgrep="$(printf '%s\n' "${other_tests[@]}")" for instance_type in "${other_instance_types[@]}"; do # On first run we usually pass the canonical test names like # cl.basic, cl.internet or *, so we decide which tests should # be run on the other instances based on this list. On the # other hand, the rerun will contain names of the failed tests # only, and those are specific - if a test failed on the main # instance, the name of the test will be like cl.basic; if a # test failed on other instance, the name of the test will be # like extra-test.[…].cl.basic. So in case of reruns, we want # to filter the extra tests first then we decide which tests # should be run. if [[ "${is_first_run}" -eq 1 ]]; then set -o noglob # noglob should not be necessary, as # query_kola_tests shouldn't return a # wildcard, but better to be safe than sorry queried_tests="$(query_kola_tests "${instance_type}" "${@}")" instance_tests=( $(grep --only-matching --fixed-strings "${other_tests_for_fgrep}" <<<"${queried_tests}" || :) ) set +o noglob else filter_prefixed_tests instance_tests "${instance_type}" "${@}" fi if [[ "${#instance_tests[@]}" -gt 0 ]]; then tests_on_instances_running=1 ( local instance_tapfile="instance_${instance_type}_validate.tap" set +e set -x local output output=$(run_kola_tests "${instance_type}" "${instance_tapfile}" "${instance_tests[@]}" 2>&1) set +x set -e local escaped_instance_type escaped_instance_type="$(sed -e 's/[\/&]/\\&/g' <<<"${instance_type}")" printf "=== START ${instance_type} ===\n%s\n=== END ${instance_type} ===\n" "$(sed -e "s/^/${escaped_instance_type}: /g" <<<"${output}")" prefix_tap_file "${instance_type}" "${instance_tapfile}" ) & fi done local main_tests=() filter_out_prefixed_tests main_tests "${@}" if [[ "${#main_tests[@]}" -gt 0 ]]; then # run in a subshell, so the set -x and set +e do not pollute # the outer environment ( set +e set -x run_kola_tests "${main_instance_type}" "${main_tapfile}" "${main_tests[@]}" true ) fi if [[ "${tests_on_instances_running}" -eq 1 ]]; then wait merge_tap_files "${main_tapfile}" 'instance_'*'_validate.tap' rm -f 'instance_'*'_validate.tap' fi }