testmark-bash

_testmark_call: run library code under set -u, restoring the caller's setting.

commit ecf703ab46c10f0577ad289dea5626afe49009de · parent 1fcf6f35d8 · browse this tree
Ximianth, 2026-10-23 23:31:57 UTC

A library bug that reads an unset variable now exits the script loudly,
whatever the caller's own setting.
`local -` (bash 4.4+) restores the caller's set options when the wrapper returns.

The suite already ran every library call under the caller's set -u,
and a scratch run of the parse, API, and testdata scripts with the caller's set -u off passes too,
so nothing in the library trips it.
diff --git a/lib/testmark.bash b/lib/testmark.bash
index 5e80e44..f5789b5 100644
--- a/lib/testmark.bash
+++ b/lib/testmark.bash
@@ -18,10 +18,12 @@ source "${BASH_SOURCE[0]%/*}/testmark/document.bash"
 ## Runs an implementation function in a known environment, whatever the caller's settings.
 ##
 ## - `LC_ALL=C`: string operations work on bytes, and `[[:space:]]` is exactly ASCII whitespace.
+## - `set -u`: a library bug that reads an unset variable exits the script loudly.  `local -` restores the caller's setting.
 ## - `nocasematch` off: pattern matches are exact.  It's restored afterwards, since `local -` doesn't cover shopts.
 ## - The `||` list: inside it, the caller's `set -e` and ERR trap can't fire on the implementation's ordinary nonzero statuses.
 _testmark_call() {
-	local LC_ALL=C _testmark_st=0 _testmark_nocase=
+	local - LC_ALL=C _testmark_st=0 _testmark_nocase=
+	set -u
 	case ":$BASHOPTS:" in *:nocasematch:*) _testmark_nocase=1 && shopt -u nocasematch ;; esac
 	"$@" || _testmark_st=$?
 	[[ -z $_testmark_nocase ]] || shopt -s nocasematch
diff --git a/tests/test-caller-env.sh b/tests/test-caller-env.sh
index 1d6e317..d69f237 100755
--- a/tests/test-caller-env.sh
+++ b/tests/test-caller-env.sh
@@ -28,4 +28,11 @@ testmark_body doc nope 2>/dev/null || rc=$?
 expect_eq "errexit: an error returns, to a caller that checks" "$rc" 1
 expect_eq "the ERR trap never fired" "$err_trap_fired" 0
 
+# The library turns on set -u for its own code only.
+set +u
+before=$-
+testmark_body doc x >/dev/null
+expect_eq "set -u: a caller without it still lacks it after a call" "$-" "$before"
+set -u
+
 exit "$RESULT"