From 519dc67095e0ed1570a0dde89c716cbc61b8ffdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Mon, 28 Sep 2026 07:35:40 +0000 Subject: [PATCH] test: a negated command only asserts in final position `! cmd` on its own line asserts nothing in a bats body unless it happens to be the last command of that body: bash does not apply errexit to a negated command, so the test's verdict is the exit status of the final command and every earlier `! cmd` is inert. shellcheck grades the two cases apart, error for the inert ones and note for the rest, which is how these were found: shellcheck -f gcc --shell=bash tests/*.bats | grep SC2314 Twenty-two of the thirty-one negations in this suite were inert, the largest group in the organization. Seventeen of them are in test-ipconfig.bats, and thirteen of those are the whole of "ip6_syntax rejects what is not an IPv6 address": only the last of the thirteen decided anything, in an IPv6 validator, in an IPv6-first distribution. The other five are test-init-fence.bats (nat_available for a family whose nat table is missing, and two processes that must be dead) and test-tagid.bats (an untagged page, and the placeholder that must not survive the copy). Every site becomes `run ! cmd`, the form that asserts wherever it stands, including the nine that were in final position and did assert: a line whose meaning depends on its position is the trap itself, and leaving half of them in the old shape leaves a reader to work out which half. That form needs bats 1.5.0, declared at the top of each file as the other suites in the organization already declare it. One site was a pipeline, `! printf ... | grep -q WARNING`, which `run` cannot take; it reads the captured output of the preceding run instead. No assertion changed its verdict: all thirteen IPv6 rejection cases reject, and the suite is green with the assertions live, 112 tests. Coverage is unchanged, 99.55 percent of measured lines (threshold 98). --- tests/test-init-fence.bats | 12 ++++++---- tests/test-ipconfig.bats | 48 ++++++++++++++++++++------------------ tests/test-run.bats | 4 +++- tests/test-tagid.bats | 6 +++-- 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/tests/test-init-fence.bats b/tests/test-init-fence.bats index 31f3382..32b5c8c 100644 --- a/tests/test-init-fence.bats +++ b/tests/test-init-fence.bats @@ -5,6 +5,8 @@ # under a scratch INITHOOKS_PATH; /etc/default/turnkey-init-fence, the # htdocs, the pid file and the log file are scratch paths. +bats_require_minimum_version 1.5.0 + load helpers REPO=$BATS_TEST_DIRNAME/.. @@ -89,7 +91,7 @@ teardown() { @test "nat_available asks the family for its nat PREROUTING chain" { fence_nat_available iptables stub_tables ip6tables 3 0 - ! fence_nat_available ip6tables + run ! fence_nat_available ip6tables [ "$(calls ip6tables)" = '-t nat -n -L PREROUTING' ] } @@ -197,7 +199,7 @@ teardown() { [ "$output" = 'Stopping init-fence mini-server' ] [ ! -e "$PIDFILE" ] sleep 0.2 - ! kill -0 "$pid" 2>/dev/null + run ! kill -0 "$pid" 2>/dev/null [ -z "$(calls pgrep)" ] } @@ -211,7 +213,7 @@ teardown() { [ "${lines[3]}" = "Found simplehttpd.py (pid: $pid) - killing" ] [ "$(calls pgrep)" = '--oldest --full /usr/lib/inithooks/bin/simplehttpd.py.*lib/inithooks/turnkey-init-fence/htdocs' ] sleep 0.2 - ! kill -0 "$pid" 2>/dev/null + run ! kill -0 "$pid" 2>/dev/null } @test "stop_mini_server reports when no server is found" { @@ -231,7 +233,7 @@ teardown() { [ "$(count ip6tables ' -A PREROUTING ')" -eq 4 ] [ "$(count iptables ' -A INPUT ')" -eq 2 ] [ "$(count ip6tables ' -A INPUT ')" -eq 2 ] - ! printf '%s\n' "${lines[@]}" | grep -q WARNING + run ! grep -q WARNING <<<"$output" [[ "$(calls simplehttpd.py)" == *"$WRITABLE_HTDOCS 60080 60443 "* ]] } @@ -287,7 +289,7 @@ teardown() { [ "$(count iptables ' -D INPUT ')" -eq 2 ] [ ! -e "$PIDFILE" ] sleep 0.2 - ! kill -0 "$pid" 2>/dev/null + run ! kill -0 "$pid" 2>/dev/null } @test "stop-post removes the rules after a failed run" { diff --git a/tests/test-ipconfig.bats b/tests/test-ipconfig.bats index 8cdf712..391f480 100644 --- a/tests/test-ipconfig.bats +++ b/tests/test-ipconfig.bats @@ -5,6 +5,8 @@ # scratch /etc/default/inithooks, preseed and interfaces file with ip, ifup, # turnkey-version and head replaced by stubs. +bats_require_minimum_version 1.5.0 + load helpers REPO=$BATS_TEST_DIRNAME/.. @@ -56,9 +58,9 @@ preseed() { } @test "valid_config rejects anything else" { - ! ipconfig_valid_config bogus - ! ipconfig_valid_config '' - ! ipconfig_valid_config 'static dhcp' + run ! ipconfig_valid_config bogus + run ! ipconfig_valid_config '' + run ! ipconfig_valid_config 'static dhcp' } @test "iface is br0 on lxc and eth0 elsewhere" { @@ -69,8 +71,8 @@ preseed() { @test "unchanged succeeds only when the stanza is already there" { ipconfig_unchanged "$INTERFACES" eth0 dhcp - ! ipconfig_unchanged "$INTERFACES" eth0 static - ! ipconfig_unchanged "$INTERFACES" br0 dhcp + run ! ipconfig_unchanged "$INTERFACES" eth0 static + run ! ipconfig_unchanged "$INTERFACES" br0 dhcp } @test "unchanged fails silently when the file is missing" { @@ -271,7 +273,7 @@ iface eth0 inet6 dhcp [ "$(calls ip)" = 'link set eth0 down' ] [ -z "$(calls ifup)" ] [ "$(tail -1 "$INTERFACES")" = ' hostname tkldev' ] - ! grep -q inet6 "$INTERFACES" + run ! grep -q inet6 "$INTERFACES" } @test "hook static without a netmask is fatal" { @@ -289,7 +291,7 @@ iface eth0 inet6 dhcp [ "$status" -eq 0 ] grep -q '^iface br0 inet manual$' "$INTERFACES" grep -q '^iface br0 inet6 dhcp$' "$INTERFACES" - ! grep -q eth0 "$INTERFACES" + run ! grep -q eth0 "$INTERFACES" [ "$(calls ip)" = 'link set br0 down' ] } @@ -304,9 +306,9 @@ iface eth0 inet6 dhcp @test "unchanged also checks the inet6 stanza when a config is given" { ipconfig_unchanged "$INTERFACES" eth0 dhcp dhcp - ! ipconfig_unchanged "$INTERFACES" eth0 dhcp static + run ! ipconfig_unchanged "$INTERFACES" eth0 dhcp static sed -i 's/inet dhcp/inet manual/' "$INTERFACES" - ! ipconfig_unchanged "$INTERFACES" eth0 dhcp dhcp + run ! ipconfig_unchanged "$INTERFACES" eth0 dhcp dhcp } @test "ip6_syntax accepts full, compressed and edge addresses" { @@ -321,19 +323,19 @@ iface eth0 inet6 dhcp } @test "ip6_syntax rejects what is not an IPv6 address" { - ! ipconfig_ip6_syntax '' - ! ipconfig_ip6_syntax 2001 - ! ipconfig_ip6_syntax 192.0.2.10 - ! ipconfig_ip6_syntax 2001:db8:1::10/64 - ! ipconfig_ip6_syntax 2001:db8:1:::10 - ! ipconfig_ip6_syntax 2001::db8::10 - ! ipconfig_ip6_syntax :2001:db8:1:2:3:4:5:6 - ! ipconfig_ip6_syntax 2001:db8:1:2:3:4:5:6: - ! ipconfig_ip6_syntax 12345::1 - ! ipconfig_ip6_syntax 2001:db8:g::1 - ! ipconfig_ip6_syntax 1:2:3:4:5:6:7 - ! ipconfig_ip6_syntax 1:2:3:4:5:6:7:8:9 - ! ipconfig_ip6_syntax 1:2:3:4:5:6:7::8 + run ! ipconfig_ip6_syntax '' + run ! ipconfig_ip6_syntax 2001 + run ! ipconfig_ip6_syntax 192.0.2.10 + run ! ipconfig_ip6_syntax 2001:db8:1::10/64 + run ! ipconfig_ip6_syntax 2001:db8:1:::10 + run ! ipconfig_ip6_syntax 2001::db8::10 + run ! ipconfig_ip6_syntax :2001:db8:1:2:3:4:5:6 + run ! ipconfig_ip6_syntax 2001:db8:1:2:3:4:5:6: + run ! ipconfig_ip6_syntax 12345::1 + run ! ipconfig_ip6_syntax 2001:db8:g::1 + run ! ipconfig_ip6_syntax 1:2:3:4:5:6:7 + run ! ipconfig_ip6_syntax 1:2:3:4:5:6:7:8:9 + run ! ipconfig_ip6_syntax 1:2:3:4:5:6:7::8 } @test "ip6_first_group pads and lowercases the first group" { @@ -512,7 +514,7 @@ iface eth0 inet6 dhcp run "$HOOK" [ "$status" -eq 0 ] grep -q '^iface eth0 inet6 dhcp$' "$INTERFACES" - ! grep -q 'address' "$INTERFACES" + run ! grep -q 'address' "$INTERFACES" } @test "hook IP6_CONFIG=dhcp writes what an unset key writes" { diff --git a/tests/test-run.bats b/tests/test-run.bats index b49de22..89ff00c 100644 --- a/tests/test-run.bats +++ b/tests/test-run.bats @@ -6,6 +6,8 @@ # job is to create the conf file gets the path before the file is there; that # is what an appliance first boot found missing. +bats_require_minimum_version 1.5.0 + load helpers setup() { @@ -106,7 +108,7 @@ EOF [ "$status" -eq 0 ] grep -q '^export ROOT_PASS=' "$CONF" grep -q '^export AUTO_RUN=TRUE' "$CONF" - ! grep -qi 'ambiguous redirect' "$ROOT/inithooks.log" + run ! grep -qi 'ambiguous redirect' "$ROOT/inithooks.log" } @test "a failing hook is logged and the run carries on" { diff --git a/tests/test-tagid.bats b/tests/test-tagid.bats index b0a9fe1..ef71524 100644 --- a/tests/test-tagid.bats +++ b/tests/test-tagid.bats @@ -6,6 +6,8 @@ # /etc/default/turnkey-init-fence and the fence htdocs, with systemctl # replaced by a stub. +bats_require_minimum_version 1.5.0 + load helpers REPO=$BATS_TEST_DIRNAME/.. @@ -63,7 +65,7 @@ exit 0' @test "is_tagged looks for the ajax host" { echo 'fence' > "$BATS_TEST_TMPDIR/index.html" - ! tagid_is_tagged "$BATS_TEST_TMPDIR/index.html" + run ! tagid_is_tagged "$BATS_TEST_TMPDIR/index.html" echo "$SCRIPTS" >> "$BATS_TEST_TMPDIR/index.html" tagid_is_tagged "$BATS_TEST_TMPDIR/index.html" } @@ -88,7 +90,7 @@ exit 0' run "$HOOK" [ "$status" -eq 0 ] [ -f "$HTDOCS/index.html" ] - ! grep -q '@APP_NAME@' "$HTDOCS/index.html" + run ! grep -q '@APP_NAME@' "$HTDOCS/index.html" grep -q 'core' "$HTDOCS/index.html" [ "$(tail -2 "$HTDOCS/index.html")" = "$SCRIPTS" ] # the packaged copy is left as shipped