To prepare for the following change, introduce the helper function _check_exclusive_functions(). Signed-off-by: Shin'ichiro Kawasaki --- check | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/check b/check index f11ce74..cd6f927 100755 --- a/check +++ b/check @@ -13,6 +13,13 @@ _error() { exit 1 } +_check_exclusive_functions() { + if declare -fF "${1}" >/dev/null && declare -fF "${2}" >/dev/null; then + _warning "${test_name} defines both ${1} and ${2}" + return 1 + fi +} + _found_test() { local test_name="$1" local explicit="$2" @@ -31,10 +38,7 @@ _found_test() { 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 + _check_exclusive_functions test test_device || return 1 if ! declare -fF test >/dev/null && ! declare -fF test_device >/dev/null; then _warning "${test_name} does not define test() or test_device()" -- 2.51.0 As to the test target devices defined in TEST_DEVS variable, blktests assumes that each test case with test_device() function is run for each single device defined in TEST_DEVS. On the other hand, it is suggested to support a test case for not a single device but multiple devices. To fulfill this request, introduce a new test function test_device_array() and TEST_CASE_DEV_ARRAY. TEST_CASE_DEV_ARRAY is an associative array. Test case names are the keys, and the lists of block devices are the values of the associative array. When a test case defines test_device_array() and users specify TEST_CASE_DEV_ARRAY in the config file for the test case, the test_device_array() is called. Blktests framework checks that each device in TEST_CASE_DEV_ARRAY is a block device, then call device_requires() and group_device_requires() for it. Blktests prepares TEST_DEV_ARRAY and TEST_DEV_ARRAY_SYSFS_DIRS which contain the list of devices and corresponding sysfs paths, so that test_device_array() can refer to these to run the test. Signed-off-by: Shin'ichiro Kawasaki --- check | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 148 insertions(+), 11 deletions(-) diff --git a/check b/check index cd6f927..eaf6853 100755 --- a/check +++ b/check @@ -26,7 +26,7 @@ _found_test() { unset CAN_BE_ZONED DESCRIPTION QUICK TIMED requires device_requires \ test test_device fallback_device cleanup_fallback_device \ - set_conditions + set_conditions test_device_array # shellcheck disable=SC1090 if ! . "tests/${test_name}"; then @@ -39,14 +39,20 @@ _found_test() { fi _check_exclusive_functions test test_device || return 1 + _check_exclusive_functions test test_device_array || return 1 + _check_exclusive_functions test_device test_device_array || return 1 - if ! declare -fF test >/dev/null && ! declare -fF test_device >/dev/null; then - _warning "${test_name} does not define test() or test_device()" + if ! declare -fF test >/dev/null && \ + ! declare -fF test_device >/dev/null && \ + ! declare -fF test_device_array >/dev/null; then + _warning "${test_name} does not define test(), test_device() or test_device_array()" 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()" + if declare -fF device_requires >/dev/null && \ + ! declare -fF test_device >/dev/null && \ + ! declare -fF test_device_array >/dev/null; then + _warning "${test_name} defines device_requires() but not either test_device() or test_device_array()" return 1 fi @@ -71,7 +77,9 @@ _found_test() { fi if (( ! explicit )); then - if [[ $DEVICE_ONLY -ne 0 ]] && ! declare -fF test_device >/dev/null; then + if [[ $DEVICE_ONLY -ne 0 ]] && \ + ! declare -fF test_device >/dev/null && \ + ! declare -fF test_device_array >/dev/null; then return fi if (( QUICK_RUN && !QUICK && !TIMED )); then @@ -518,6 +526,75 @@ _check_and_call_test_device() { return $ret } +_get_test_device_array_key() { + local key + + for key in "${!TEST_CASE_DEV_ARRAY[@]}"; do + if [[ $TEST_NAME == "$key" ]]; then + echo "$TEST_NAME" + return + fi + done + + for key in "${!TEST_CASE_DEV_ARRAY[@]}"; do + if [[ $TEST_NAME =~ $key ]]; then + echo "$key" + return + fi + done + + return 1 +} + +_check_and_call_test_device_array() { + local array_name="" key + local ret=0 i + local -a devs sysfs_dirs + + if declare -fF requires >/dev/null; then + requires + fi + + TEST_DEV_ARRAY=() + if key=$(_get_test_device_array_key); then + IFS=" " read -r -a devs <<< "${TEST_CASE_DEV_ARRAY[$key]}" + IFS=" " read -r -a sysfs_dirs <<< \ + "${TEST_CASE_DEV_ARRAY_SYSFS_DIRS[$key]}" + for ((i = 0; i < "${#devs[@]}"; i++)); do + TEST_DEV=${devs[$i]} + TEST_DEV_SYSFS=${sysfs_dirs[$i]} + if (( !CAN_BE_ZONED )) && _test_dev_is_zoned; then + SKIP_REASONS+=("${TEST_DEV} is a zoned block device") + elif declare -fF device_requires >/dev/null; then + device_requires + fi + TEST_DEV_ARRAY+=("$TEST_DEV") + TEST_DEV_ARRAY_SYSFS_DIRS[$TEST_DEV]=$TEST_DEV_SYSFS + [[ -n $array_name ]] && array_name+="_" + array_name+="$(basename "$TEST_DEV")" + done + fi + + if ((${#TEST_DEV_ARRAY[@]} == 0)); then + SKIP_REASONS+=("TEST_CASE_DEV_ARRAY is not defined for ${TEST_NAME}") + fi + + unset TEST_DEV + unset TEST_DEV_SYSFS + RESULTS_DIR="$OUTPUT/${array_name}${postfix}" + # Avoid "if" and "!" for errexit in test cases + _call_test test_device_array + # shellcheck disable=SC2181 + (($? != 0)) && ret=1 + + TEST_DEV_ARRAY=() + for ((i = 0; i < "${#devs[@]}"; i++)); do + unset "TEST_DEV_ARRAY_SYSFS_DIRS[${devs[i]}]" + done + + return $ret +} + _run_test() { TEST_NAME="$1" CAN_BE_ZONED=0 @@ -554,7 +631,7 @@ _run_test() { _check_and_call_test ret=$? fi - else + elif declare -fF test_device >/dev/null; then if [[ ${#TEST_DEVS[@]} -eq 0 ]] && \ declare -fF fallback_device >/dev/null; then if ! test_dev=$(fallback_device); then @@ -594,6 +671,9 @@ _run_test() { unset "TEST_DEV_PART_SYSFS_DIRS[${TEST_DEVS[0]}]" TEST_DEVS=() fi + else + _check_and_call_test_device_array + ret=$? fi _unload_modules @@ -604,6 +684,8 @@ _run_test() { _run_group() { local tests=("$@") local group="${tests["0"]%/*}" + local test_name + local -a devs sysfs_dirs # shellcheck disable=SC1090 . "tests/${group}/rc" @@ -637,6 +719,33 @@ _run_group() { # Fix the array indices. TEST_DEVS=("${TEST_DEVS[@]}") unset TEST_DEV + + for test_name in "${!TEST_CASE_DEV_ARRAY[@]}"; do + if [[ ! $test_name =~ ^${group}/ ]]; then + continue + fi + IFS=" " read -r -a devs <<< \ + "${TEST_CASE_DEV_ARRAY[$test_name]}" + IFS=" " read -r -a sysfs_dirs <<< \ + "${TEST_CASE_DEV_ARRAY_SYSFS_DIRS[$test_name]}" + for ((i = 0; i < ${#devs[@]}; i++)); do + TEST_DEV="${devs[$i]}" + TEST_DEV_SYSFS="${sysfs_dirs[$i]}" + unset TEST_DEV_PART_SYSFS + group_device_requires + if [[ -v SKIP_REASONS ]]; then + _output_notrun "${group}/*** => $(basename "$TEST_DEV")" + unset "devs[$i]" + unset "sysfs_dirs[$i]" + unset SKIP_REASONS + fi + done + # Fix the array elements. + TEST_CASE_DEV_ARRAY["$test_name"]="${devs[*]}" + TEST_CASE_DEV_ARRAY_SYSFS_DIRS["$test_name"]="${sysfs_dirs[*]}" + unset TEST_DEV + unset TEST_DEV_SYSFS + done fi local ret=0 @@ -652,7 +761,8 @@ _run_group() { _find_sysfs_dirs() { local test_dev="$1" - local sysfs_path + local array_test_name="$2" + local sysfs_path sysfs_paths local major=$((0x$(stat -L -c '%t' "$test_dev"))) local minor=$((0x$(stat -L -c '%T' "$test_dev"))) @@ -661,7 +771,12 @@ _find_sysfs_dirs() { return 1 fi - if [[ -r "${sysfs_path}"/partition ]]; then + if [[ -n $array_test_name ]]; then + sysfs_paths=${TEST_CASE_DEV_ARRAY_SYSFS_DIRS["$array_test_name"]} + [[ -n $sysfs_paths ]] && sysfs_paths="${sysfs_paths} " + sysfs_paths+="$sysfs_path" + TEST_CASE_DEV_ARRAY_SYSFS_DIRS["$array_test_name"]="${sysfs_paths}" + elif [[ -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. @@ -676,23 +791,45 @@ _find_sysfs_dirs() { declare -A TEST_DEV_SYSFS_DIRS declare -A TEST_DEV_PART_SYSFS_DIRS +declare -a TEST_DEV_ARRAY +# shellcheck disable=SC2034 +declare -A TEST_DEV_ARRAY_SYSFS_DIRS +declare -A TEST_CASE_DEV_ARRAY +declare -A TEST_CASE_DEV_ARRAY_SYSFS_DIRS _check() { # shellcheck disable=SC2034 SRCDIR="$(realpath src)" - local test_dev - for test_dev in "${TEST_DEVS[@]}"; do + local test_dev test_name + local -a all_test_devs_in_array + + for test_name in "${!TEST_CASE_DEV_ARRAY[@]}"; do + IFS=" " read -r -a all_test_devs_in_array <<< \ + "${TEST_CASE_DEV_ARRAY[$test_name]}" + done + + for test_dev in "${TEST_DEVS[@]}" "${all_test_devs_in_array[@]}"; 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 + done + for test_dev in "${TEST_DEVS[@]}"; do if ! _find_sysfs_dirs "$test_dev"; then _error "could not find sysfs directory for ${test_dev}" fi done + for test_name in "${!TEST_CASE_DEV_ARRAY[@]}"; do + for test_dev in ${TEST_CASE_DEV_ARRAY[$test_name]}; do + if ! _find_sysfs_dirs "$test_dev" "$test_name"; then + _error "could not find sysfs directory for ${test_dev}" + fi + done + done + local test_name group prev_group local tests=() local ret=0 -- 2.51.0 The previous commit introduced the test_device_array() function which allows a test case to use multiple block devices. Describe how to implement it in the 'new' script. Also describe how to specify test target block devices in Documentation/running-tests.md. This reverts commit 5623805f5a07ea0be4c3916ec68301c469aee843. --- Documentation/running-tests.md | 14 ++++++++++++++ new | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/Documentation/running-tests.md b/Documentation/running-tests.md index a42fc91..f037634 100644 --- a/Documentation/running-tests.md +++ b/Documentation/running-tests.md @@ -42,6 +42,20 @@ If `TEST_DEVS` is not defined or is empty, only tests which do not require a device will be run. If `TEST_DEVS` is defined as a normal variable instead of an array, it will be converted to an array by splitting on whitespace. +Some test cases require multiple block devices for single test run. These test +cases implement a special test function test_device_array(). TEST_CASE_DEV_ARRAY +is an associative array which defines test devices for such test cases. In this +array, each key represents a test case name or a regular expression to match +test case names. Each key's corresponding value is a list of devices associated +with the test case. The test cases run for all of the devices specified in the +list. Again, note that tests are destructive and will overwrite any data on +these devices. + +```sh +TEST_CASE_DEV_ARRAY[md/003]="/dev/nvme0n1 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1" +TEST_CASE_DEV_ARRAY[meta/02*]="/dev/nvme0n1 /dev/nvme1n1" +``` + ### Excluding Tests diff --git a/new b/new index d84f01d..df4092c 100755 --- a/new +++ b/new @@ -245,6 +245,15 @@ DESCRIPTION="" # is a partition device (e.g., # /sys/block/sda/sda1). If the device is not a # partition, this is an empty string. +# +# Tests that require multiple test devices should rename test() to +# test_device_array(). These tests will be run with variables defined below +# instead of TEST_DEV and TEST_DEV_SYSFS: +# - \$TEST_DEV_ARRAY -- the block devices to run the test on. The devices are +# taken from TEST_CASE_DEV_ARRAY defined in the config. +# - \$TEST_DEV_ARRAY_SYSFS -- the sysfs directories of the devices in the +# form of an associative array. Use values in +# TEST_DEV_ARRAY as the keys. test() { echo "Running \${TEST_NAME}" -- 2.51.0 While I worked on the previous commit, I noticed that there is a room to improve descriptions about TEST_DEVS. Add more descriptions about it in Documentation/running-tests.md. Also note the relation between TEST_DEVS and the test_device() function in the 'new' script. This reverts commit 6aa89879d4b8a79a54589b53fe287fd188ca2131. --- Documentation/running-tests.md | 13 ++++++++----- new | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Documentation/running-tests.md b/Documentation/running-tests.md index f037634..8ccd739 100644 --- a/Documentation/running-tests.md +++ b/Documentation/running-tests.md @@ -30,17 +30,20 @@ options have precedence over the configuration file. ### Test Devices -The `TEST_DEVS` variable is an array of block devices to test on. Tests will be -run on all of these devices where applicable. Note that tests are destructive -and will overwrite any data on these devices. +Some test cases require a block device for testing. These test cases implement +a special test function test_device(). The `TEST_DEVS` variable is an array of +block devices that such test cases to test on. Every test will be run on each of +these devices where applicable. Note that tests are destructive and will +overwrite any data on these devices. ```sh TEST_DEVS=(/dev/nvme0n1 /dev/sdb) ``` If `TEST_DEVS` is not defined or is empty, only tests which do not require a -device will be run. If `TEST_DEVS` is defined as a normal variable instead of -an array, it will be converted to an array by splitting on whitespace. +device will be run, which implments the test function 'test()'. If `TEST_DEVS` +is defined as a normal variable instead of an array, it will be converted to an +array by splitting on whitespace. Some test cases require multiple block devices for single test run. These test cases implement a special test function test_device_array(). TEST_CASE_DEV_ARRAY diff --git a/new b/new index df4092c..4773175 100755 --- a/new +++ b/new @@ -235,7 +235,8 @@ DESCRIPTION="" # # Tests that require a test device should rename test() to test_device(). These # tests will be run with a few more variables defined: -# - \$TEST_DEV -- the block device to run the test on (e.g., /dev/sda1). +# - \$TEST_DEV -- the block device to run the test on (e.g., /dev/sda1). The +# device is taken from TEST_DEVS in the config for each run. # - \$TEST_DEV_SYSFS -- the sysfs directory of the device (e.g., # /sys/block/sda). In general, you should use the # _test_dev_queue_{get,set} helpers. If the device is a -- 2.51.0 The previous commit introduced test_device_array(). Add five test cases to check its functionality. These test cases require the line below in the config: TEST_CASE_DEV_ARRAY[meta/02?]=/dev/XX /dev/XX should be a valid block device. Signed-off-by: Shin'ichiro Kawasaki --- tests/meta/020 | 14 ++++++++++++++ tests/meta/020.out | 2 ++ tests/meta/021 | 15 +++++++++++++++ tests/meta/021.out | 2 ++ tests/meta/022 | 17 +++++++++++++++++ tests/meta/022.out | 2 ++ tests/meta/023 | 17 +++++++++++++++++ tests/meta/023.out | 2 ++ tests/meta/024 | 13 +++++++++++++ tests/meta/024.out | 2 ++ 10 files changed, 86 insertions(+) create mode 100755 tests/meta/020 create mode 100644 tests/meta/020.out create mode 100755 tests/meta/021 create mode 100644 tests/meta/021.out create mode 100755 tests/meta/022 create mode 100644 tests/meta/022.out create mode 100755 tests/meta/023 create mode 100644 tests/meta/023.out create mode 100755 tests/meta/024 create mode 100644 tests/meta/024.out diff --git a/tests/meta/020 b/tests/meta/020 new file mode 100755 index 0000000..38fa920 --- /dev/null +++ b/tests/meta/020 @@ -0,0 +1,14 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0+ +# Copyright (C) 2025 Western Digital Corporation or its affiliates. +# +# Test test_device_array() + +. tests/meta/rc + +DESCRIPTION="do nothing in test_device_array()" + +test_device_array() { + echo "Running ${TEST_NAME}" + echo "Test complete" +} diff --git a/tests/meta/020.out b/tests/meta/020.out new file mode 100644 index 0000000..35e7722 --- /dev/null +++ b/tests/meta/020.out @@ -0,0 +1,2 @@ +Running meta/020 +Test complete diff --git a/tests/meta/021 b/tests/meta/021 new file mode 100755 index 0000000..731e6b3 --- /dev/null +++ b/tests/meta/021 @@ -0,0 +1,15 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0+ +# Copyright (C) 2025 Western Digital Corporation or its affiliates. +# +# Test test_device_array() + +. tests/meta/rc + +DESCRIPTION="exit with non-zero status from test_device_array()" + +test_device_array() { + echo "Running ${TEST_NAME}" + echo "Test complete" + return 1 +} diff --git a/tests/meta/021.out b/tests/meta/021.out new file mode 100644 index 0000000..25ee0fc --- /dev/null +++ b/tests/meta/021.out @@ -0,0 +1,2 @@ +Running meta/021 +Test complete diff --git a/tests/meta/022 b/tests/meta/022 new file mode 100755 index 0000000..8a853e7 --- /dev/null +++ b/tests/meta/022 @@ -0,0 +1,17 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0+ +# Copyright (C) 2025 Western Digital Corporation or its affiliates. +# +# Test skip in device_requries() for test_device_array() + +. tests/meta/rc + +DESCRIPTION="skip test_device_array() in device_requries()" + +device_requires() { + SKIP_REASONS+=("(╯°□°)╯︵ $TEST_DEV ┻━┻") +} + +test_device_array() { + echo '¯\_(ツ)_/¯' +} diff --git a/tests/meta/022.out b/tests/meta/022.out new file mode 100644 index 0000000..c335c68 --- /dev/null +++ b/tests/meta/022.out @@ -0,0 +1,2 @@ +Running meta/022 +Test complete diff --git a/tests/meta/023 b/tests/meta/023 new file mode 100755 index 0000000..646c216 --- /dev/null +++ b/tests/meta/023 @@ -0,0 +1,17 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0+ +# Copyright (C) 2025 Western Digital Corporation or its affiliates. +# +# Test requires() with test_device_array() + +. tests/meta/rc + +DESCRIPTION="skip test_device_array() in requires()" + +requires() { + SKIP_REASONS+=("(╯°□°)╯︵ ┻━┻") +} + +test_device_array() { + echo '¯\_(ツ)_/¯' +} diff --git a/tests/meta/023.out b/tests/meta/023.out new file mode 100644 index 0000000..a3b3c66 --- /dev/null +++ b/tests/meta/023.out @@ -0,0 +1,2 @@ +Running meta/023 +Test complete diff --git a/tests/meta/024 b/tests/meta/024 new file mode 100755 index 0000000..5d3b29e --- /dev/null +++ b/tests/meta/024 @@ -0,0 +1,13 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0+ +# Copyright (C) 2025 Western Digital Corporation or its affiliates. +# +# Test skipping from test_device_array() + +. tests/meta/rc + +DESCRIPTION="skip in test_device_array()" + +test_device_array() { + SKIP_REASONS+=("(╯°□°)╯︵ ┻━┻") +} diff --git a/tests/meta/024.out b/tests/meta/024.out new file mode 100644 index 0000000..c5be615 --- /dev/null +++ b/tests/meta/024.out @@ -0,0 +1,2 @@ +Running meta/024 +Test complete -- 2.51.0