testmark-bash

parse: hide bash's own message when the file can't be opened, so the only output is our prefixed line.

commit ee3f738e3aa7d3ec69e089c622fc8d00a106f6cd · parent 764825d0a7 · browse this tree
Ximianth, 2026-10-23 23:54:14 UTC

The 2>/dev/null goes before the input redirect: redirects apply left to right, so in the other order the open fails before stderr is redirected, and bash's message still reaches the caller.
Checked on bash 4.4 through 5.3.

expect_fail no longer needs to cut bash's file-and-line prefix, so that goes too.
diff --git a/lib/testmark/document.bash b/lib/testmark/document.bash
index d40ad71..2fe6e98 100644
--- a/lib/testmark/document.bash
+++ b/lib/testmark/document.bash
@@ -35,7 +35,8 @@ _testmark_parse() {
 	for kind in "${_testmark_kinds[@]}"; do unset -v "_testmark_${kind}_$h"; done
 	local -a lines
 	# See "use blocks to open an fd and check properties on it without TOCTOU" in docs/hacking/bash-pitfalls.md.
-	{ [[ ! -d /dev/stdin ]] && mapfile lines; } <"$file" || {
+	# The 2>/dev/null comes before the <, so it also hides bash's message when the open fails; ours is printed instead.
+	{ [[ ! -d /dev/stdin ]] && mapfile lines; } 2>/dev/null <"$file" || {
 		_testmark_err "$file: not a readable file"
 		return 1
 	}
diff --git a/tests/test-api.sh b/tests/test-api.sh
index 55d45f6..d91b3c7 100755
--- a/tests/test-api.sh
+++ b/tests/test-api.sh
@@ -6,19 +6,17 @@ cd "$(dirname "$0")"
 source ./lib.sh
 
 ## Runs a command that should fail, and checks it printed exactly the given error and nothing on stdout.
-## A message from bash itself starts with the library file and line it came from; that prefix is cut, so library edits don't move it.
 ##
 ## - @param $1 description
 ## - @param $2 the expected stderr, without its final newline
 ## - @param $3... the command
 expect_fail() {
-	local rc=0 err
+	local rc=0
 	"${@:3}" >"$work/out" 2>"$work/err" || rc=$?
 	if [[ $rc == 0 ]]; then
 		_fail "$1: succeeded; want a failure"
 	else
-		err=$(<"$work/err")
-		expect_eq "$1" "${err#"$ROOT/lib/"*": line "*": "}$(<"$work/out")" "$2"
+		expect_eq "$1" "$(<"$work/err")$(<"$work/out")" "$2"
 	fi
 }
 
@@ -62,7 +60,7 @@ expect_fail "re-parse drops the old doc's names" 'testmark: a: no hunk named "ol
 expect_eq "re-parse leaves other handles alone" "$(testmark_body b x)" "two"
 expect_ok "re-parse of the same file" testmark_parse b b.md
 expect_eq "and it reads" "$(testmark_body b x)" "two"
-expect_fail "a failed re-parse fails" 'missing.md: No such file or directory'$'\n''testmark: missing.md: not a readable file' testmark_parse b missing.md
+expect_fail "a failed re-parse fails" 'testmark: missing.md: not a readable file' testmark_parse b missing.md
 expect_fail "and leaves its handle unusable" 'testmark: unknown handle "b"' testmark_names b
 
 # --- what a parse doesn't keep is gone: white-box, comparing every library global and its value ---