| #!/bin/bash |
| # SPDX-License-Identifier: GPL-3.0+ |
| # Copyright (C) 2017 Omar Sandoval |
| |
| shopt -s extglob |
| |
| _warning() { |
| echo "$0: $*" >&2 |
| } |
| |
| _error() { |
| echo "$0: $*" >&2 |
| exit 1 |
| } |
| |
| _found_test() { |
| local test_name="$1" |
| local explicit="$2" |
| |
| unset CAN_BE_ZONED DESCRIPTION QUICK TIMED requires device_requires test test_device fallback_device cleanup_fallback_device |
| |
| # shellcheck disable=SC1090 |
| if ! . "tests/${test_name}"; then |
| return 1 |
| fi |
| |
| if [[ -z $DESCRIPTION ]]; then |
| _warning "${test_name} does not define DESCRIPTION" |
| return 1 |
| fi |
| |
| if declare -fF test >/dev/null && declare -fF test_device >/dev/null; then |
| _warning "${test_name} defines both test() and test_device()" |
| return 1 |
| fi |
| |
| if ! declare -fF test >/dev/null && ! declare -fF test_device >/dev/null; then |
| _warning "${test_name} does not define test() or test_device()" |
| return 1 |
| fi |
| |
| if declare -fF device_requires >/dev/null && ! declare -fF test_device >/dev/null; then |
| _warning "${test_name} defines device_requires() but not test_device()" |
| return 1 |
| fi |
| |
| if declare -fF fallback_device >/dev/null && ! declare -fF cleanup_fallback_device >/dev/null; then |
| _warning "${test_name} defines fallback_device() but not cleanup_fallback_device()" |
| return 1 |
| fi |
| |
| if declare -fF cleanup_fallback_device >/dev/null && ! declare -fF fallback_device >/dev/null; then |
| _warning "${test_name} defines cleanup_fallback_device() but not fallback_device()" |
| return 1 |
| fi |
| |
| if (( QUICK && TIMED )); then |
| _warning "${test_name} cannot be both QUICK and TIMED" |
| return 1 |
| fi |
| |
| if (( ! explicit )); then |
| if [[ $DEVICE_ONLY -ne 0 ]] && ! declare -fF test_device >/dev/null; then |
| return |
| fi |
| if (( QUICK_RUN && !QUICK && !TIMED )); then |
| return |
| fi |
| if [[ -n ${EXCLUDE["$test_name"]} ]]; then |
| return |
| fi |
| fi |
| |
| printf '%s\0' "$test_name" |
| } |
| |
| _found_group() { |
| local group="$1" |
| local explicit="$2" |
| |
| local test_path |
| |
| if (( ! explicit )); then |
| if [[ -n ${EXCLUDE["$group"]} ]]; then |
| return |
| fi |
| fi |
| |
| while IFS= read -r -d '' test_path; do |
| _found_test "${test_path#tests/}" 0 |
| done < <(find "tests/$group" -type f -name '[0-9][0-9][0-9]' -print0) |
| } |
| |
| _find_tests() { |
| if [[ $# -eq 0 ]]; then |
| # No filters given, run all tests except for the meta group. |
| local group_path |
| while IFS= read -r -d '' group_path; do |
| if [[ $group_path != tests/meta ]]; then |
| _found_group "${group_path#tests/}" 0 |
| fi |
| done < <(find tests -mindepth 2 -type f -name rc -printf '%h\0') |
| else |
| local filter |
| for filter in "$@"; do |
| # A filter is bad if it: |
| # - Is an absolute path |
| # - Is a relative path containing ".." |
| if [[ $filter =~ ^/|(^|/)\.\.(/|$) ]]; then |
| _warning "bad filter ${filter}" |
| continue |
| fi |
| # Remove leading and internal "." components. |
| filter="${filter##+(./)}" |
| filter="${filter//\/+(.\/)/\/}" |
| # Remove repeated "/". |
| filter="${filter//\/+(\/)/\/}" |
| # Remove leading "tests/" |
| filter="${filter#tests/}" |
| |
| if [[ -d tests/$filter ]]; then |
| _found_group "$filter" 1 |
| elif [[ -f tests/$filter ]]; then |
| _found_test "$filter" 1 |
| else |
| _warning "no group or test named ${filter}" |
| fi |
| done |
| fi |
| } |
| |
| _check_dmesg() { |
| local dmesg_marker="$1" |
| local seqres="${RESULTS_DIR}/${TEST_NAME}" |
| |
| if [[ $CHECK_DMESG -eq 0 ]]; then |
| return 0 |
| fi |
| |
| dmesg | bash -c "$DMESG_FILTER" | grep -A 9999 "$dmesg_marker" >"${seqres}.dmesg" |
| grep -q -e "kernel BUG at" \ |
| -e "WARNING:" \ |
| -e "BUG:" \ |
| -e "Oops:" \ |
| -e "possible recursive locking detected" \ |
| -e "Internal error" \ |
| -e "INFO: suspicious RCU usage" \ |
| -e "INFO: possible circular locking dependency detected" \ |
| -e "general protection fault:" \ |
| -e "blktests failure" \ |
| "${seqres}.dmesg" |
| # shellcheck disable=SC2181 |
| if [[ $? -eq 0 ]]; then |
| return 1 |
| else |
| rm -f "${seqres}.dmesg" |
| return 0 |
| fi |
| } |
| |
| # Associative arrays are local by default. declare -g was added in 4.2, but it |
| # was apparently broken for associative arrays initially. |
| declare -A LAST_TEST_RUN |
| declare -A TEST_RUN |
| |
| _read_last_test_run() { |
| local seqres="${RESULTS_DIR}/${TEST_NAME}" |
| |
| LAST_TEST_RUN["date"]="" |
| LAST_TEST_RUN["status"]="" |
| LAST_TEST_RUN["reason"]="" |
| LAST_TEST_RUN["exit_status"]="" |
| LAST_TEST_RUN["runtime"]="" |
| |
| if [[ ! -e $seqres ]]; then |
| return |
| fi |
| |
| local key value |
| while IFS=$'\t' read -r key value; do |
| LAST_TEST_RUN["$key"]="$value" |
| done <"$seqres" |
| } |
| |
| _write_test_run() { |
| local key value |
| for key in "${!TEST_RUN[@]}"; do |
| value="${TEST_RUN["$key"]}" |
| printf '%s\t%s\n' "$key" "$value" >>"$seqres" |
| done |
| } |
| |
| _output_status() { |
| local test="$1" |
| local status="$2" |
| local zoned=" " |
| |
| if (( RUN_FOR_ZONED )); then zoned=" (zoned) "; fi |
| |
| if [[ "${DESCRIPTION:-}" ]]; then |
| printf '%-60s' "${test}${zoned}($DESCRIPTION)" |
| else |
| printf '%-60s' "${test}${zoned}" |
| fi |
| if [[ -z $status ]]; then |
| echo |
| return |
| fi |
| |
| echo -n " [" |
| if [[ -t 1 ]]; then |
| case "$status" in |
| passed) |
| tput setaf 2 |
| ;; |
| failed) |
| tput setaf 1 |
| ;; |
| "not run") |
| tput setaf 3 |
| ;; |
| esac |
| fi |
| echo -n "$status" |
| if [[ -t 1 ]]; then |
| tput sgr0 |
| fi |
| echo "]" |
| } |
| |
| _output_notrun() { |
| _output_status "$1" "not run" |
| echo " $SKIP_REASON" |
| } |
| |
| _output_last_test_run() { |
| if [[ "${TEST_DEV:-}" ]]; then |
| _output_status "$TEST_NAME => $(basename "$TEST_DEV")" "" |
| else |
| _output_status "$TEST_NAME" "" |
| fi |
| |
| ( |
| local key value |
| while IFS= read -r key; do |
| if [[ $key =~ ^date|status|reason|exit_status$ ]]; then |
| continue |
| fi |
| value="${LAST_TEST_RUN["$key"]}" |
| printf ' %s\t%s\t...\n' "${key}" "${value}" |
| done < <(printf '%s\n' "${!LAST_TEST_RUN[@]}" | sort) |
| ) | column -t -s $'\t' |
| } |
| |
| _output_test_run() { |
| if [[ -t 1 ]]; then |
| # -4 for date, status, reason, and exit_status, which we don't |
| # output, +1 for the test name. |
| tput cuu $((${#LAST_TEST_RUN[@]} - 3)) |
| fi |
| |
| if [[ "${TEST_DEV:-}" ]]; then |
| _output_status "$TEST_NAME => $(basename "$TEST_DEV")" "${TEST_RUN["status"]}ed" |
| else |
| _output_status "$TEST_NAME" "${TEST_RUN["status"]}ed" |
| fi |
| |
| ( |
| local key last_value value |
| while IFS= read -r key; do |
| if [[ $key =~ ^date|status|reason|exit_status$ ]]; then |
| continue |
| fi |
| last_value="${LAST_TEST_RUN["$key"]}" |
| value="${TEST_RUN["$key"]}" |
| printf ' %s\t%s\t...\t%s\n' "$key" "$last_value" "$value" |
| done < <(printf '%s\n' "${!LAST_TEST_RUN[@]}" | sort) |
| |
| while IFS= read -r key; do |
| # [[ -v array[key] ]] was added in Bash 4.3. Do it this ugly |
| # way to support older versions. |
| if [[ -n ${LAST_TEST_RUN["$key"]} || ${LAST_TEST_RUN["$key"]-unset} != unset ]]; then |
| continue |
| fi |
| value="${TEST_RUN["$key"]}" |
| printf ' %s\t\t...\t%s\n' "$key" "$value" |
| done < <(printf '%s\n' "${!TEST_RUN[@]}" | sort) |
| ) | column -t -s $'\t' |
| } |
| |
| _register_test_cleanup() { |
| TEST_CLEANUP=$1 |
| } |
| |
| _cleanup() { |
| if [[ -v TEST_CLEANUP ]]; then |
| ${TEST_CLEANUP} |
| fi |
| |
| if [[ "${TMPDIR:-}" ]]; then |
| rm -rf "$TMPDIR" |
| unset TMPDIR |
| fi |
| |
| local key value |
| for key in "${!TEST_DEV_QUEUE_SAVED[@]}"; do |
| value="${TEST_DEV_QUEUE_SAVED["$key"]}" |
| echo "$value" >"${TEST_DEV_SYSFS}/queue/${key}" |
| unset TEST_DEV_QUEUE_SAVED["$key"] |
| done |
| |
| if [[ "${RESTORE_CPUS_ONLINE:-}" ]]; then |
| local cpu |
| for cpu in "${!CPUS_ONLINE_SAVED[@]}"; do |
| echo "${CPUS_ONLINE_SAVED["$cpu"]}" >"/sys/devices/system/cpu/cpu$cpu/online" |
| done |
| unset RESTORE_CPUS_ONLINE |
| fi |
| |
| _exit_cgroup2 |
| } |
| |
| _call_test() { |
| local test_func="$1" |
| local seqres="${RESULTS_DIR}/${TEST_NAME}" |
| # shellcheck disable=SC2034 |
| FULL="${seqres}.full" |
| declare -A TEST_DEV_QUEUE_SAVED |
| |
| _read_last_test_run |
| _output_last_test_run |
| |
| TEST_RUN["date"]="$(date "+%F %T")" |
| |
| mkdir -p "$(dirname "$seqres")" |
| # Remove leftovers from last time. |
| rm -f "${seqres}" "${seqres}."* |
| |
| if [[ -w /dev/kmsg ]]; then |
| local dmesg_marker="run blktests $TEST_NAME at ${TEST_RUN["date"]}" |
| echo "$dmesg_marker" >> /dev/kmsg |
| else |
| local dmesg_marker="" |
| CHECK_DMESG=0 |
| fi |
| $LOGGER_PROG "run blktests $TEST_NAME" |
| |
| unset TEST_CLEANUP |
| trap _cleanup EXIT |
| if ! TMPDIR="$(mktemp --tmpdir -p "$OUTPUT" -d "tmpdir.${TEST_NAME//\//.}.XXX")"; then |
| return |
| fi |
| |
| TIMEFORMAT="%Rs" |
| pushd . >/dev/null || return |
| { time "$test_func" >"${seqres}.out" 2>&1; } 2>"${seqres}.runtime" |
| TEST_RUN["exit_status"]=$? |
| popd >/dev/null || return |
| TEST_RUN["runtime"]="$(cat "${seqres}.runtime")" |
| rm -f "${seqres}.runtime" |
| |
| _cleanup |
| |
| if ! diff "tests/${TEST_NAME}.out" "${seqres}.out" >/dev/null; then |
| mv "${seqres}.out" "${seqres}.out.bad" |
| TEST_RUN["status"]=fail |
| TEST_RUN["reason"]=output |
| elif [[ ${TEST_RUN["exit_status"]} -ne 0 ]]; then |
| TEST_RUN["status"]=fail |
| TEST_RUN["reason"]="exit" |
| elif ! _check_dmesg "$dmesg_marker"; then |
| TEST_RUN["status"]=fail |
| TEST_RUN["reason"]=dmesg |
| else |
| TEST_RUN["status"]=pass |
| fi |
| rm -f "${seqres}.out" |
| |
| _write_test_run |
| _output_test_run |
| |
| if [[ ${TEST_RUN["status"]} = pass ]]; then |
| return 0 |
| else |
| case "${TEST_RUN["reason"]}" in |
| output) |
| diff -u "tests/${TEST_NAME}.out" "${seqres}.out.bad" | awk " |
| { |
| if (NR > 10) { |
| print \" ...\" |
| print \" (Run 'diff -u tests/${TEST_NAME}.out ${seqres}.out.bad' to see the entire diff)\" |
| exit |
| } |
| print \" \" \$0 |
| }" |
| ;; |
| exit) |
| echo " exited with status ${TEST_RUN["exit_status"]}" |
| ;; |
| dmesg) |
| echo " something found in dmesg:" |
| awk " |
| { |
| if (NR > 10) { |
| print \" ...\" |
| print \" (See '${seqres}.dmesg' for the entire message)\" |
| exit |
| } |
| print \" \" \$0 |
| }" "${seqres}.dmesg" |
| ;; |
| esac |
| return 1 |
| fi |
| } |
| |
| _test_dev_is_zoned() { |
| if [[ ! -f "${TEST_DEV_SYSFS}/queue/zoned" ]] || |
| grep -q none "${TEST_DEV_SYSFS}/queue/zoned"; then |
| SKIP_REASON="${TEST_DEV} is not a zoned block device" |
| return 1 |
| fi |
| return 0 |
| } |
| |
| _run_test() { |
| TEST_NAME="$1" |
| CAN_BE_ZONED=0 |
| CHECK_DMESG=1 |
| DMESG_FILTER="cat" |
| RUN_FOR_ZONED=0 |
| FALLBACK_DEVICE=0 |
| |
| # shellcheck disable=SC1090 |
| . "tests/${TEST_NAME}" |
| |
| if declare -fF test >/dev/null; then |
| if declare -fF requires >/dev/null && ! requires; then |
| _output_notrun "$TEST_NAME" |
| return 0 |
| fi |
| |
| RESULTS_DIR="$OUTPUT/nodev" |
| _call_test test |
| local ret=$? |
| if (( RUN_ZONED_TESTS && CAN_BE_ZONED )); then |
| RESULTS_DIR="$OUTPUT/nodev_zoned" |
| RUN_FOR_ZONED=1 |
| _call_test test |
| ret=$(( ret || $? )) |
| fi |
| return $ret |
| else |
| if [[ ${#TEST_DEVS[@]} -eq 0 ]] && \ |
| declare -fF fallback_device >/dev/null; then |
| if ! test_dev=$(fallback_device); then |
| _warning "$TEST_NAME: fallback_device call failure" |
| return 0 |
| fi |
| |
| if ! _find_sysfs_dirs "$test_dev"; then |
| _warning "$TEST_NAME: could not find sysfs directory for ${test_dev}" |
| cleanup_fallback_device |
| return 0 |
| fi |
| TEST_DEVS=( "${test_dev}" ) |
| FALLBACK_DEVICE=1 |
| fi |
| |
| if [[ ${#TEST_DEVS[@]} -eq 0 ]]; then |
| return 0 |
| fi |
| |
| if declare -fF requires >/dev/null && ! requires; then |
| _output_notrun "$TEST_NAME" |
| return 0 |
| fi |
| |
| local ret=0 |
| for TEST_DEV in "${TEST_DEVS[@]}"; do |
| TEST_DEV_SYSFS="${TEST_DEV_SYSFS_DIRS["$TEST_DEV"]}" |
| TEST_DEV_PART_SYSFS="${TEST_DEV_PART_SYSFS_DIRS["$TEST_DEV"]}" |
| if (( !CAN_BE_ZONED )) && _test_dev_is_zoned; then |
| SKIP_REASON="${TEST_DEV} is a zoned block device" |
| _output_notrun "$TEST_NAME => $(basename "$TEST_DEV")" |
| continue |
| fi |
| unset SKIP_REASON |
| if declare -fF device_requires >/dev/null && ! device_requires; then |
| _output_notrun "$TEST_NAME => $(basename "$TEST_DEV")" |
| continue |
| fi |
| RESULTS_DIR="$OUTPUT/$(basename "$TEST_DEV")" |
| if ! _call_test test_device; then |
| ret=1 |
| fi |
| done |
| |
| if (( FALLBACK_DEVICE )); then |
| cleanup_fallback_device |
| unset TEST_DEV_SYSFS_DIRS["${TEST_DEVS[0]}"] |
| unset TEST_DEV_PART_SYSFS_DIRS["${TEST_DEVS[0]}"] |
| TEST_DEVS=() |
| fi |
| |
| return $ret |
| fi |
| } |
| |
| _run_group() { |
| local tests=("$@") |
| local group="${tests["0"]%/*}" |
| |
| # shellcheck disable=SC1090 |
| . "tests/${group}/rc" |
| |
| if declare -fF group_requires >/dev/null && ! group_requires; then |
| _output_notrun "${group}/***" |
| return 0 |
| fi |
| |
| if declare -fF group_device_requires >/dev/null; then |
| local i |
| for i in "${!TEST_DEVS[@]}"; do |
| TEST_DEV="${TEST_DEVS[$i]}" |
| TEST_DEV_SYSFS="${TEST_DEV_SYSFS_DIRS["$TEST_DEV"]}" |
| # shellcheck disable=SC2034 |
| TEST_DEV_PART_SYSFS="${TEST_DEV_PART_SYSFS_DIRS["$TEST_DEV"]}" |
| if ! group_device_requires; then |
| _output_notrun "${group}/*** => $(basename "$TEST_DEV")" |
| unset TEST_DEVS["$i"] |
| fi |
| done |
| # Fix the array indices. |
| TEST_DEVS=("${TEST_DEVS[@]}") |
| unset TEST_DEV |
| fi |
| |
| local ret=0 |
| local test_name |
| for test_name in "${tests[@]}"; do |
| if ! ( _run_test "$test_name" ); then |
| ret=1 |
| fi |
| done |
| return $ret |
| } |
| |
| _find_sysfs_dirs() { |
| local test_dev="$1" |
| local sysfs_path |
| local major=$((0x$(stat -L -c '%t' "$test_dev"))) |
| local minor=$((0x$(stat -L -c '%T' "$test_dev"))) |
| |
| # Get the canonical sysfs path |
| if ! sysfs_path="$(realpath "/sys/dev/block/${major}:${minor}")"; then |
| return 1 |
| fi |
| |
| if [[ -r "${sysfs_path}"/partition ]]; then |
| # If the device is a partition device, cut the last device name |
| # of the canonical sysfs path to access to the sysfs of its |
| # holder device. |
| # e.g. .../block/sda/sda1 -> ...block/sda |
| TEST_DEV_SYSFS_DIRS["$test_dev"]="${sysfs_path%/*}" |
| TEST_DEV_PART_SYSFS_DIRS["$test_dev"]="${sysfs_path}" |
| else |
| TEST_DEV_SYSFS_DIRS["$test_dev"]="${sysfs_path}" |
| TEST_DEV_PART_SYSFS_DIRS["$test_dev"]="" |
| fi |
| } |
| |
| declare -A TEST_DEV_SYSFS_DIRS |
| declare -A TEST_DEV_PART_SYSFS_DIRS |
| _check() { |
| # shellcheck disable=SC2034 |
| SRCDIR="$(realpath src)" |
| |
| local test_dev |
| for test_dev in "${TEST_DEVS[@]}"; do |
| if [[ ! -e $test_dev ]]; then |
| _error "${test_dev} does not exist" |
| elif [[ ! -b $test_dev ]]; then |
| _error "${test_dev} is not a block device" |
| fi |
| |
| if ! _find_sysfs_dirs "$test_dev"; then |
| _error "could not find sysfs directory for ${test_dev}" |
| fi |
| done |
| |
| local test_name group prev_group |
| local tests=() |
| local ret=0 |
| while IFS= read -r -d '' test_name; do |
| group="${test_name%/*}" |
| if [[ $group != "$prev_group" ]]; then |
| prev_group="$group" |
| if [[ ${#tests[@]} -gt 0 ]]; then |
| if ! ( _run_group "${tests[@]}" ); then |
| ret=1 |
| fi |
| tests=() |
| fi |
| fi |
| tests+=("$test_name") |
| done < <(_find_tests "$@" | sort -zu) |
| |
| if [[ ${#tests[@]} -gt 0 ]]; then |
| if ! ( _run_group "${tests[@]}" ); then |
| ret=1 |
| fi |
| fi |
| |
| return $ret |
| } |
| |
| usage () { |
| USAGE_STRING="\ |
| usage: $0 [options] [group-or-test...] |
| |
| Run blktests |
| |
| Test runs: |
| -d, --device-only only run tests which use a test device from the |
| TEST_DEVS config setting |
| |
| -o, --output=DIR output results to the given directory (the default is |
| ./results) |
| |
| -q, --quick=SECONDS do a quick run (only run quick tests and limit the |
| runtime of longer tests to the given timeout, |
| defaulting to 30 seconds) |
| |
| -x, --exclude=TEST exclude a test (or test group) from the list of |
| tests to run |
| |
| Miscellaneous: |
| -c, --config=FILE load configuration from FILE instead of the default |
| (\"./config\"); if this option is used multiple times, |
| all of the given files are loaded |
| |
| -h, --help display this help message and exit" |
| |
| case "$1" in |
| out) |
| echo "$USAGE_STRING" |
| exit 0 |
| ;; |
| err) |
| echo "$USAGE_STRING" >&2 |
| exit 1 |
| ;; |
| esac |
| } |
| |
| if ! TEMP=$(getopt -o 'do:q::x:c:h' --long 'device-only,quick::,exclude:,output:,config:,help' -n "$0" -- "$@"); then |
| exit 1 |
| fi |
| |
| eval set -- "$TEMP" |
| unset TEMP |
| |
| LOADED_CONFIG=0 |
| OPTION_EXCLUDE=() |
| while true; do |
| case "$1" in |
| '-d'|'--device-only') |
| OPTION_DEVICE_ONLY=1 |
| shift |
| ;; |
| '-o'|'--output') |
| OPTION_OUTPUT="$2" |
| shift 2 |
| ;; |
| '-q'|'--quick') |
| OPTION_QUICK_RUN=1 |
| OPTION_TIMEOUT="$2" |
| shift 2 |
| ;; |
| '-x'|'--exclude') |
| # shellcheck disable=SC2190 |
| OPTION_EXCLUDE+=("$2") |
| shift 2 |
| ;; |
| '-c'|'--config') |
| # shellcheck source=/dev/null |
| . "$2" |
| LOADED_CONFIG=1 |
| shift 2 |
| ;; |
| '-h'|'--help') |
| usage out |
| ;; |
| '--') |
| shift |
| break |
| ;; |
| *) |
| usage err |
| ;; |
| esac |
| done |
| |
| # Load the default configuration file if "-c" was not used. |
| if [[ $LOADED_CONFIG -eq 0 && -r config ]]; then |
| # shellcheck source=/dev/null |
| . config |
| fi |
| |
| # Options on the command line take precedence over the configuration file. If |
| # neither set the option and we didn't get it from the environment, then we |
| # fall back to the default. |
| DEVICE_ONLY="${OPTION_DEVICE_ONLY:-${DEVICE_ONLY:-0}}" |
| |
| OUTPUT="${OPTION_OUTPUT:-${OUTPUT:-results}}" |
| |
| QUICK_RUN="${OPTION_QUICK_RUN:-${QUICK_RUN:-0}}" |
| if [[ -n $OPTION_QUICK_RUN && $OPTION_QUICK_RUN -ne 0 ]]; then |
| TIMEOUT="${OPTION_TIMEOUT:-${TIMEOUT:-30}}" |
| fi |
| |
| if [[ "${EXCLUDE:-}" ]] && ! declare -p EXCLUDE | grep -q '^declare -a'; then |
| # If EXCLUDE was not defined as an array, convert it to one. |
| # shellcheck disable=SC2128,SC2190,SC2206 |
| EXCLUDE=($EXCLUDE) |
| elif [[ ! "${EXCLUDE:-}" ]]; then |
| EXCLUDE=() |
| fi |
| # Convert the exclude list to an associative array. The lists from the |
| # configuration file and command line options are merged. |
| TEMP_EXCLUDE=("${EXCLUDE[@]}" "${OPTION_EXCLUDE[@]}") |
| unset EXCLUDE |
| declare -A EXCLUDE |
| for filter in "${TEMP_EXCLUDE[@]}"; do |
| EXCLUDE["$filter"]=1 |
| done |
| unset OPTION_EXCLUDE TEMP_EXCLUDE |
| |
| if [[ "${TEST_DEVS:-}" ]] && ! declare -p TEST_DEVS | grep -q '^declare -a'; then |
| # If TEST_DEVS was not defined as an array, convert it to one. |
| # shellcheck disable=SC2128,SC2206 |
| TEST_DEVS=($TEST_DEVS) |
| elif [[ ! "${TEST_DEVS:-}" ]]; then |
| TEST_DEVS=() |
| fi |
| |
| : "${LOGGER_PROG:="$(type -P logger || echo true)"}" |
| : "${RUN_ZONED_TESTS:=0}" |
| |
| # Sanity check options. |
| if [[ $QUICK_RUN -ne 0 && ! "${TIMEOUT:-}" ]]; then |
| _error "QUICK_RUN specified without TIMEOUT" |
| fi |
| |
| if [[ $DEVICE_ONLY -ne 0 && ${#TEST_DEVS[@]} -eq 0 ]]; then |
| _error "DEVICE_ONLY specified without TEST_DEVS" |
| fi |
| |
| mkdir -p "$OUTPUT" |
| OUTPUT="$(realpath "$OUTPUT")" |
| |
| _check "$@" |