#!/usr/bin/env bash # # regression tests for helpers.bash # # Some of those helper functions are fragile, and we don't want to break # anything if we have to mess with them. # source "$(dirname $0)"/helpers.bash source "$(dirname $0)"/helpers.network.bash die() { echo "$(basename $0): $*" >&2 exit 1 } # Iterator and return code; updated in check_result() testnum=0 rc=0 # Possibly used by the code we're testing PODMAN_TMPDIR=$(mktemp -d --tmpdir=${TMPDIR:-/tmp} podman_helper_tests.XXXXXX) trap 'rm -rf $PODMAN_TMPDIR' 0 ############################################################################### # BEGIN test the parse_table helper function check_result { testnum=$(expr $testnum + 1) if [ "$1" = "$2" ]; then # Multi-level echo flattens newlines, makes success messages readable echo $(echo "ok $testnum $3 = $1") else echo "not ok $testnum $3" echo "# expected: $2" echo "# actual: $1" rc=1 fi } # IMPORTANT NOTE: you have to do # this: while ... done < <(parse_table) # and not: parse_table | while read ... # # ...because piping to 'while' makes it a subshell, hence testnum and rc # will not be updated. # while read x y z; do check_result "$x" "a" "parse_table simple: column 1" check_result "$y" "b" "parse_table simple: column 2" check_result "$z" "c" "parse_table simple: column 3" done < <(parse_table "a | b | c") # More complicated example, with spaces while read x y z; do check_result "$x" "a b" "parse_table with spaces: column 1" check_result "$y" "c d" "parse_table with spaces: column 2" check_result "$z" "e f g" "parse_table with spaces: column 3" done < <(parse_table "a b | c d | e f g") # Multi-row, with spaces and with blank lines table=" a | b | c d e d e f | g h | i j " declare -A expect=( [0,0]="a" [0,1]="b" [0,2]="c d e" [1,0]="d e f" [1,1]="g h" [1,2]="i j" ) row=0 while read x y z;do check_result "$x" "${expect[$row,0]}" "parse_table multi_row[$row,0]" check_result "$y" "${expect[$row,1]}" "parse_table multi_row[$row,1]" check_result "$z" "${expect[$row,2]}" "parse_table multi_row[$row,2]" row=$(expr $row + 1) done < <(parse_table "$table") # Backslash handling. The first element should have none, the second some while read x y;do check_result "$x" '[0-9]{2}' "backslash test - no backslashes" check_result "$y" '[0-9]\{3\}' "backslash test - one backslash each" done < <(parse_table "[0-9]{2} | [0-9]\\\{3\\\}") # Empty strings. I wish we could convert those to real empty strings. while read x y z; do check_result "$x" "''" "empty string - left-hand" check_result "$y" "''" "empty string - middle" check_result "$z" "''" "empty string - right" done < <(parse_table " | |") # Quotes while read x y z;do check_result "$x" "a 'b c'" "single quotes" check_result "$y" "d \"e f\" g" "double quotes" check_result "$z" "h" "no quotes" # FIXME FIXME FIXME: this is the only way I can find to get bash-like # splitting of tokens. It really should be done inside parse_table # but I can't find any way of doing so. If you can find a way, please # update this test and any BATS tests that rely on quoting. eval set "$x" check_result "$1" "a" "single quotes - token split - 1" check_result "$2" "b c" "single quotes - token split - 2" check_result "$3" "" "single quotes - token split - 3" eval set "$y" check_result "$1" "d" "double quotes - token split - 1" check_result "$2" "e f" "double quotes - token split - 2" check_result "$3" "g" "double quotes - token split - 3" done < <(parse_table "a 'b c' | d \"e f\" g | h") # Split on '|' only when bracketed by spaces or at beginning/end of line while read x y z;do check_result "$x" "|x" "pipe in strings - pipe at start" check_result "$y" "y|y1" "pipe in strings - pipe in middle" check_result "$z" "z|" "pipe in strings - pipe at end" done < <(parse_table "|x | y|y1 | z|") # END test the parse_table helper ############################################################################### # BEGIN dprint function dprint_test_1() { dprint "$*" } # parse_table works, might as well use it # # | | # table=" | | debug unset dprint_test | - | substring match dprint_test_1 | - | exact match dprint_test_10 | | caller name mismatch xxx yyy zzz | | multiple callers, no match dprint_test_1 xxx yyy zzz | - | multiple callers, match at start xxx dprint_test_1 yyy zzz | - | multiple callers, match in middle xxx yyy zzz dprint_test_1 | - | multiple callers, match at end " while read var expect name; do random_string=$(random_string 20) PODMAN_TEST_DEBUG="$var" result=$(dprint_test_1 "$random_string" 3>&1) expect_full="" if [ -n "$expect" -a "$expect" != "''" ]; then expect_full="# dprint_test_1() : $random_string" fi check_result "$result" "$expect_full" "DEBUG='$var' - $name" done < <(parse_table "$table") # END dprint ############################################################################### # BEGIN remove_same_dev_warning # Test-helper function: runs remove_same_dev_warning, compares resulting # value of $lines and $output to expected values given on command line function check_same_dev() { local testname="$1"; shift local -a expect_lines=("$@") local nl=" " remove_same_dev_warning # After processing, check the expected number of lines check_result "${#lines[@]}" "${#@}" "$testname: expected # of lines" # ...and each expected line local expect_output="" local i=0 while [ $i -lt ${#expect_lines[@]} ]; do check_result "${lines[$i]}" "${expect_lines[$i]}" "$testname: line $i" expect_output+="${expect_lines[$i]}$nl" i=$(( i + 1 )) done # ...and the possibly-multi-line $output check_result "$output" "${expect_output%%$nl}" "$testname: output" } # Simplest case: nothing removed. declare -a lines=("a b c" "d" "e f") check_same_dev "abc" "a b c" "d" "e f" # Confirm that the warning message is removed from the beginning declare -a lines=( "WARNING: The same type, major and minor should not be used for multiple devices." "a" "b" "c" ) check_same_dev "warning is removed" a b c # ...and from the middle (we do not expect to see this) declare -a lines=( "WARNING: The same type, major and minor should not be used for multiple devices." "a" "b" "WARNING: The same type, major and minor should not be used for multiple devices." "c" ) check_same_dev "multiple warnings removed" a b c # Corner case: two lines of output, only one of which we care about declare -a lines=( "WARNING: The same type, major and minor should not be used for multiple devices." "this is the only line we care about" ) check_same_dev "one-line output" "this is the only line we care about" # Corner case: one line of output, but we expect zero. declare -a lines=( "WARNING: The same type, major and minor should not be used for multiple devices." ) check_same_dev "zero-line output" # END remove_same_dev_warning ############################################################################### # BEGIN random_free_port # Assumes that 16700 is open found=$(random_free_port 16700-16700) check_result "$found" "16700" "random_free_port" # END random_free_port ############################################################################### # BEGIN ipv6_to_procfs # Table of IPv6 short forms and their procfs equivalents. For readability, # spaces separate each 16-bit word. Spaces are removed when testing. table=" 2b06::1 | 2B06 0000 0000 0000 0000 0000 0000 0001 ::1 | 0000 0000 0000 0000 0000 0000 0000 0001 0::1 | 0000 0000 0000 0000 0000 0000 0000 0001 " while read shortform expect; do actual=$(ipv6_to_procfs $shortform) check_result "$actual" "${expect// }" "ipv6_to_procfs $shortform" done < <(parse_table "$table") # END ipv6_to_procfs ############################################################################### # BEGIN subnet_in_use ... because that's complicated # Override ip command function ip() { echo "default foo" echo "192.168.0.0/16" echo "172.17.2.3/30" echo "172.128.0.0/9" } # x.y.z | result (1 = in use, 0 = not in use - opposite of exit code) table=" 172 | 0 | 0 | 0 172 | 0 | 255 | 0 172 | 1 | 1 | 0 172 | 1 | 2 | 0 172 | 1 | 3 | 0 172 | 17 | 1 | 0 172 | 17 | 2 | 1 172 | 17 | 3 | 0 172 | 127 | 0 | 0 172 | 128 | 0 | 1 172 | 255 | 2 | 1 192 | 168 | 1 | 1 " while read n1 n2 n3 expect; do subnet_in_use $n1 $n2 $n3 actual=$? check_result "$((1 - $actual))" "$expect" "subnet_in_use $n1.$n2.$n3" done < <(parse_table "$table") unset -f ip # END subnet_in_use ############################################################################### # BEGIN check_assert # # This is way, way more complicated than it should be. The purpose is # to generate readable error messages should any of the tests ever fail. # # Args: the last one is "" (expect to pass) or non-"" (expect that as msg). # All other args are what we feed to assert() function check_assert() { local argv=("$@") testnum=$(expr $testnum + 1) # Final arg: "" to expect pass, anything else is expected error message local expect="${argv[-1]}" unset 'argv[-1]' # Descriptive test name. If multiline, use sed to make the rest '[...]' local testname="assert ${argv[*]}" testname="$(sed -z -e 's/[\r\n].\+/ [...]/' <<<"$testname")" # HERE WE GO. This is the actual test. actual=$(assert "${argv[@]}" 2>&1) status=$? # Now compare actual to expect. if [[ -z "$expect" ]]; then # expect: pass if [[ $status -eq 0 ]]; then # got: pass echo "ok $testnum $testname" else # got: fail echo "not ok $testnum $testname" echo "# expected success; got:" local -a actual_split IFS=$'\n' read -rd '' -a actual_split <<<"$actual" || true if [[ "${actual_split[0]}" =~ 'vvvvv' ]]; then unset 'actual_split[0]' unset 'actual_split[1]' unset 'actual_split[-1]' actual_split=("${actual_split[@]}") fi for line in "${actual_split[@]}"; do echo "# $line" done rc=1 fi else # expect: fail if [[ $status -eq 0 ]]; then # got: pass echo "not ok $testnum $testname" echo "# expected it to fail, but it passed" rc=1 else # Expected failure, got failure. But is it the desired failure? # Split what we got into lines, and remove the top/bottom borders local -a actual_split IFS=$'\n' read -rd '' -a actual_split <<<"$actual" || true if [[ "${actual_split[0]}" =~ 'vvvvv' ]]; then unset 'actual_split[0]' unset 'actual_split[1]' unset 'actual_split[-1]' actual_split=("${actual_split[@]}") fi # Split the expect string into lines, and remove first if empty local -a expect_split IFS=$'\n' read -rd '' -a expect_split <<<"$expect" || true if [[ -z "${expect_split[0]}" ]]; then unset 'expect_split[0]' expect_split=("${expect_split[@]}") fi if [[ "${actual_split[*]}" = "${expect_split[*]}" ]]; then # Yay. echo "ok $testnum $testname" else # Nope. Mismatch between actual and expected output echo "not ok $testnum $testname" rc=1 # Ugh, this is complicated. Try to produce a useful err msg. local n_e=${#expect_split[*]} local n_a=${#actual_split[*]} local n_max=${n_e} if [[ $n_max -lt $n_a ]]; then n_max=${n_a} fi printf "# %-35s | actual\n" "expect" printf "# ----------------------------------- | ------\n" for i in $(seq 0 $((${n_max}-1))); do local e="${expect_split[$i]}" local a="${actual_split[$i]}" local same=' ' local eq='=' if [[ "$e" != "$a" ]]; then same='!' eq='|' fi printf "# %s %-35s %s %s\n" "$same" "$e" "$eq" "$a" done fi fi fi } # Positive tests check_assert "a" = "a" "" check_assert "abc" =~ "a" "" check_assert "abc" =~ "b" "" check_assert "abc" =~ "c" "" check_assert "abc" =~ "a.*c" "" check_assert "a" != "b" "" # Simple Failure tests check_assert "a" = "b" " #| expected: = b #| actual: a" # This is the one that triggered #17509 expect="abcd efg hijk lmnop" actual="abcd efg hijk lmnop" check_assert "$actual" = "$expect" " #| expected: = abcd efg #| > hijk lmnop #| actual: abcd efg #| > '' #| > hijk lmnop" # Undesired carriage returns cr=$'\r' expect="this is line 1 this is line 2" actual="this is line 1$cr this is line 2$cr" check_assert "$actual" = "$expect" " #| expected: = this is line 1 #| > this is line 2 #| actual: \$'this is line 1\r' #| > \$'this is line 2\r'" # Anchored expressions; the 2nd and 3rd are 15 and 17 characters, not 16 check_assert "0123456789abcdef" =~ "^[0-9a-f]{16}\$" "" check_assert "0123456789abcde" =~ "^[0-9a-f]{16}\$" " #| expected: =~ \^\[0-9a-f\]\{16\}\\$ #| actual: 0123456789abcde" check_assert "0123456789abcdeff" =~ "^[0-9a-f]{16}\$" " #| expected: =~ \^\[0-9a-f\]\{16\}\\$ #| actual: 0123456789abcdeff" # END check_assert ############################################################################### exit $rc