From a78a7d6133b8c2b93cafc11b39e68b2d8aa7c536 Mon Sep 17 00:00:00 2001 From: Jan Dubois Date: Sat, 8 Jul 2023 13:12:00 -0700 Subject: [PATCH] Create unit tests for BATS helper functions Signed-off-by: Jan Dubois --- bats/scripts/bats-lint.pl | 2 +- bats/tests/helpers/utils.bash | 37 ++--- bats/tests/helpers/utils.bats | 263 ++++++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+), 19 deletions(-) create mode 100644 bats/tests/helpers/utils.bats diff --git a/bats/scripts/bats-lint.pl b/bats/scripts/bats-lint.pl index 94cdf3a3d8e..488a71d1378 100755 --- a/bats/scripts/bats-lint.pl +++ b/bats/scripts/bats-lint.pl @@ -27,7 +27,7 @@ # - $assert_success # - $ {assert}_success # - if [ $status -eq 0 ] - if (/(\$\{?)?(assert|refute|output\b|status\b)/) { + if (/(\$\{?)?(assert|refute|\boutput\b|\bstatus\b)/) { undef $run; } # Doesn't match on: diff --git a/bats/tests/helpers/utils.bash b/bats/tests/helpers/utils.bash index 296cd357cf3..e2f9d7a1103 100644 --- a/bats/tests/helpers/utils.bash +++ b/bats/tests/helpers/utils.bash @@ -1,11 +1,7 @@ is_true() { # case-insensitive check; false values: '', '0', 'no', and 'false' local value="$(echo "$1" | tr '[:upper:]' '[:lower:]')" - if [[ $value =~ ^(0|no|false)?$ ]]; then - false - else - true - fi + [[ ! $value =~ ^(0|no|false)?$ ]] } is_false() { @@ -26,7 +22,7 @@ validate_enum() { local var=$1 shift for value in "$@"; do - if [ "${!var}" = "$value" ]; then + if [[ ${!var} == "$value" ]]; then return fi done @@ -40,7 +36,13 @@ assert_nothing() { } jq_output() { - jq -r "$@" <<<"${output}" + # -e sets exit status to 1 when the output is false or null + run jq -e -r "$@" <<<"${output}" + echo "$output" + if [[ $status == 1 && $output == false ]]; then + status=0 + fi + return "$status" } get_setting() { @@ -90,10 +92,10 @@ try() { shift done - local count - for ((count = 0; count < max; ++count)); do + local count=0 + while true; do run "$@" - if ((status == 0)); then + if ((status == 0 || ++count >= max)); then break fi sleep "$delay" @@ -106,7 +108,7 @@ image_without_tag() { local image=$1 # If the tag looks like a port number and follows something that looks # like a domain name, then don't strip the tag (e.g. foo.io:5000). - if [[ ${image##*:} =~ ^[0-9]+$ && ${image%:*} =~ \.[a-z]+$ ]]; then + if [[ ${image##*:} =~ ^[0-9]+(/|$) && ${image%:*} =~ \.[a-z]+$ ]]; then echo "$image" else echo "${image%:*}" @@ -156,22 +158,21 @@ unique_filename() { local suffix="" while true; do - local filename="$basename$suffix$extension" - if [ ! -e "$filename" ]; then + local filename="${basename}${suffix}${extension}" + if [[ ! -e $filename ]]; then echo "$filename" return fi - index=$((index + 1)) - suffix="_$index" + suffix="_$((++index))" done } capture_logs() { - if capturing_logs && [ -d "$PATH_LOGS" ]; then + if capturing_logs && [[ -d $PATH_LOGS ]]; then local logdir=$(unique_filename "${PATH_BATS_LOGS}/${RD_TEST_FILENAME}") mkdir -p "$logdir" - cp -LR "$PATH_LOGS/" "$logdir" - echo "${BATS_TEST_DESCRIPTION:-teardown}" >"$logdir/test_description" + cp -LR "${PATH_LOGS}/" "$logdir" + echo "${BATS_TEST_DESCRIPTION:-teardown}" >"${logdir}/test_description" fi } diff --git a/bats/tests/helpers/utils.bats b/bats/tests/helpers/utils.bats new file mode 100644 index 00000000000..03519d3560a --- /dev/null +++ b/bats/tests/helpers/utils.bats @@ -0,0 +1,263 @@ +load '../helpers/load' + +######################################################################## + +local_setup() { + COUNTER="${BATS_FILE_TMPDIR}/counter" + echo 0 >"$COUNTER" +} + +# Increment counter file. Return success when counter >= max. +inc_counter() { + local max=${1-9999} + local counter=$(($(cat "$COUNTER") + 1)) + echo $counter >"$COUNTER" + ((counter >= max)) +} + +assert_counter_is() { + run cat "${COUNTER}" + assert_output "$1" +} + +######################################################################## + +check_truthiness() { + local predicate=$1 + local value + + # test true values + for value in 1 true True TRUE yes Yes YES any; do + run "$predicate" "$value" + if [[ $predicate == is_true ]]; then + assert_success + else + assert_failure + fi + done + + # test false values + for value in 0 false False FALSE no No NO ''; do + run "$predicate" "$value" + if [[ $predicate == is_true ]]; then + assert_failure + else + assert_success + fi + done +} + +@test 'is_true' { + check_truthiness is_true +} + +@test 'is_false' { + check_truthiness is_false +} + +@test 'bool' { + run bool true + assert_output true + + run bool false + assert_output false +} + +######################################################################## + +@test 'validate_enum OS should pass' { + run validate_enum OS darwin linux windows + assert_success +} + +@test 'validate_enum FRUIT should fail' { + FRUIT=apple + run validate_enum FRUIT banana cherry pear + assert_failure + # Can't check output; it is written using "fatal": + # FRUIT=apple is not a valid setting; select from [banana cherry pear] +} + +######################################################################## + +@test 'is_xxx' { + # Exactly one of the is_xxx functions should return true + count=0 + for os in linux macos windows; do + if "is_$os"; then + ((++count)) + fi + done + ((count == 1)) +} + +######################################################################## + +get_json_test_data() { + # The run/assert silliness is because shellcheck gets confused by direct assignment to $output + run echo '{"String":"string", "False":false, "Null":null}' + assert_success +} + +@test 'jq_output extracts string value' { + get_json_test_data + run jq_output .String + assert_success + assert_output string +} + +@test 'jq_output extracts "false" value' { + get_json_test_data + run jq_output .False + assert_success + assert_output false +} + +@test 'jq_output cannot extract "null" value' { + get_json_test_data + run jq_output .Null + assert_failure + assert_output null +} + +@test 'jq_output fails when key is not found' { + get_json_test_data + run jq_output .DoesNotExist + assert_failure + assert_output null +} + +@test 'jq_output fails on null' { + output=null + run jq_output .Anything + assert_failure + assert_output null +} + +@test 'jq_output fails on undefined' { + output=undefined + run jq_output .Anything + assert_failure + assert_output --partial "parse error" +} + +@test 'jq_output fails on non-JSON data' { + output="This is not JSON" + run jq_output .Anything + assert_failure + assert_output --partial "parse error" +} + +######################################################################## + +@test 'this_function' { + foo() { + this_function + } + run foo + assert_success + assert_output foo +} + +@test 'calling_function' { + bar() { + baz + } + baz() { + calling_function + } + run bar + assert_success + assert_output bar +} + +######################################################################## + +@test 'try will run command at least once' { + run try --max 0 --delay 5 inc_counter + assert_failure + assert_counter_is 1 + # "try" should not have called "sleep 5" at all + ((SECONDS < 2)) +} + +@test 'try will stop as soon as the command succeeds' { + run try --max 3 --delay 3 inc_counter 2 + assert_success + assert_counter_is 2 + # "try" should have called "sleep 3" exactly once + ((SECONDS >= 3)) + ((SECONDS < 6)) +} + +@test 'try will return after max retries' { + run try --max 3 --delay 2 inc_counter + assert_failure + assert_counter_is 3 + # "try" should have called "sleep 2" exactly twice + ((SECONDS >= 4)) + ((SECONDS < 6)) +} + +######################################################################## + +check_image_without_tag() { + local image=$1 + local expect=${2-$1} + run image_without_tag "$image" + assert_success + assert_output "$expect" +} + +@test 'image_without_tag busybox' { + check_image_without_tag busybox +} + +@test 'image_without_tag busybox:latest' { + check_image_without_tag busybox:latest busybox +} + +@test 'image_without_tag busybox:5000' { + check_image_without_tag busybox:5000 busybox +} + +@test 'image_without_tag registry.io:5000' { + check_image_without_tag registry.io:5000 +} + +@test 'image_without_tag registry.io:5000/busybox' { + check_image_without_tag registry.io:5000/busybox +} + +@test 'image_without_tag registry.io:5000/busybox:8080' { + check_image_without_tag registry.io:5000/busybox:8080 registry.io:5000/busybox +} + +######################################################################## + +@test 'unique_filename without extension' { + run unique_filename "$COUNTER" + assert_success + assert_output "${COUNTER}_2" + touch "$output" + + run unique_filename "$COUNTER" + assert_success + assert_output "${COUNTER}_3" +} + +@test 'unique_filename with extension' { + run unique_filename "$COUNTER" .png + assert_success + assert_output "${COUNTER}.png" + touch "$output" + + run unique_filename "$COUNTER" .png + assert_success + assert_output "${COUNTER}_2.png" + touch "$output" + + run unique_filename "$COUNTER" .png + assert_success + assert_output "${COUNTER}_3.png" +}