git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 8/9] test-lib: allow test snippets as here-docs

From
Jeff King <peff@peff.net>
Date
Jul 10, 2024, 08:39 UTC
Message-ID
<20240710083929.GH2060601@coredump.intra.peff.net>
In-Reply-To
<20240710083416.GA2060328@coredump.intra.peff.net>
Most test snippets are wrapped in single quotes, like:
  test_expect_success 'some description' '
          do_something
  '

This sometimes makes the snippets awkward to write, because you can't easily use single quotes within them. We sometimes work around this with $SQ, or by loosening regexes to use "." instead of a literal quote, or by using double quotes when we'd prefer to use single-quotes (and just adding extra backslash-escapes to avoid interpolation).

This commit adds another option: feeding the snippet via the function's stdin. This doesn't conflict with anything the snippet would want to do, because we always redirect its stdin from /dev/null anyway (which we'll continue to do).

A few notes on the implementation:
  - it would be nice to push this down into test_run_, but we can't, as
    test_expect_success and test_expect_failure want to see the actual
    script content to report it for verbose-mode. A helper function
    limits the amount of duplication in those callers here.
  - The helper function is a little awkward to call, as you feed it the
    name of the variable you want to set. The more natural thing in
    shell would be command substitution like:
      body=$(body_or_stdin "$2")
    but that loses trailing whitespace. There are tricks around this,
    like:
      body=$(body_or_stdin "$2"; printf .)
      body=${body%.}
    but we'd prefer to keep such tricks in the helper, not in each
    caller.
  - I implemented the helper using a sequence of "read" calls. Together
    with "-r" and unsetting the IFS, this preserves incoming whitespace.
    An alternative is to use "cat" (which then requires the gross "."
    trick above). But this saves us a process, which is probably a good
    thing. The "read" builtin does use more read() syscalls than
    necessary (one per byte), but that is almost certainly a win over a
    separate process.
    Both are probably slower than passing a single-quoted string, but
    the difference is lost in the noise for a script that I converted as
    an experiment.
  - I handle test_expect_success and test_expect_failure here. If we
    like this style, we could easily extend it to other spots (e.g.,
    lazy_prereq bodies) on top of this patch.
  - even though we are using "local", we have to be careful about our
    variable names. Within test_expect_success, any variable we declare
    with local will be seen as local by the test snippets themselves (so
    it wouldn't persist between tests like normal variables would).
Signed-off-by: Jeff King <peff@peff.net>
---
 t/README                |  8 ++++++++
 t/test-lib-functions.sh | 32 +++++++++++++++++++++++++++-----
 2 files changed, 35 insertions(+), 5 deletions(-)
diff --git a/t/README b/t/README
index d9e0e07506..dec644f997 100644
--- a/t/README
+++ b/t/README
@@ -906,6 +906,14 @@ see test-lib-functions.sh for the full list and their options.
 	    'git-write-tree should be able to write an empty tree.' \
 	    'tree=$(git-write-tree)'
 
+   If <script> is `-` (a single dash), then the script to run is read
+   from stdin. This lets you more easily use single quotes within the
+   script by using a here-doc. For example:
+
+	test_expect_success 'output contains expected string' - <<\EOT
+		grep "this string has 'quotes' in it" output
+	EOT
+
    If you supply three parameters the first will be taken to be a
    prerequisite; see the test_set_prereq and test_have_prereq
    documentation below:
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 1ea9f31225..fde9bf54fc 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -872,6 +872,24 @@ test_verify_prereq () {
 	BUG "'$test_prereq' does not look like a prereq"
 }
 
+# assign the variable named by "$1" with the contents of "$2";
+# if "$2" is "-", then read stdin into "$1" instead
+test_body_or_stdin () {
+	if test "$2" != "-"
+	then
+		eval "$1=\$2"
+		return
+	fi
+
+	# start with a newline, to match hanging newline from open-quote style
+	eval "$1=\$LF"
+	local test_line
+	while IFS= read -r test_line
+	do
+		eval "$1=\${$1}\${test_line}\${LF}"
+	done
+}
+
 test_expect_failure () {
 	test_start_ "$@"
 	test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
@@ -881,9 +899,11 @@ test_expect_failure () {
 	export test_prereq
 	if ! test_skip "$@"
 	then
+		local test_body
+		test_body_or_stdin test_body "$2"
 		test -n "$test_skip_test_preamble" ||
-		say >&3 "checking known breakage of $TEST_NUMBER.$test_count '$1': $2"
-		if test_run_ "$2" expecting_failure
+		say >&3 "checking known breakage of $TEST_NUMBER.$test_count '$1': $test_body"
+		if test_run_ "$test_body" expecting_failure
 		then
 			test_known_broken_ok_ "$1"
 		else
@@ -902,13 +922,15 @@ test_expect_success () {
 	export test_prereq
 	if ! test_skip "$@"
 	then
+		local test_body
+		test_body_or_stdin test_body "$2"
 		test -n "$test_skip_test_preamble" ||
-		say >&3 "expecting success of $TEST_NUMBER.$test_count '$1': $2"
-		if test_run_ "$2"
+		say >&3 "expecting success of $TEST_NUMBER.$test_count '$1': $test_body"
+		if test_run_ "$test_body"
 		then
 			test_ok_ "$1"
 		else
-			test_failure_ "$@"
+			test_failure_ "$1" "$test_body"
 		fi
 	fi
 	test_finish_
-- 
2.45.2.1249.gb036353db5
Previous: Jeff KingNext: Jeff King
Message 61 of 65 in “here-doc test bodies”
  1. 0/2 here-doc test bodiesJeff King, Jul 1, 2024
  2. 1/2 test-lib: allow test snippets as here-docsJeff King, Jul 1, 2024
  3. Eric SunshineJul 1, 2024
  4. Junio C HamanoJul 1, 2024
  5. Jeff KingJul 2, 2024
  6. Jeff KingJul 2, 2024
  7. Eric SunshineJul 2, 2024
  8. Jeff KingJul 6, 2024
  9. Jeff KingJul 2, 2024
  10. Eric SunshineJul 2, 2024
  11. Jeff KingJul 6, 2024
  12. Eric SunshineJul 2, 2024
  13. Eric SunshineJul 2, 2024
  14. Eric SunshineJul 2, 2024
  15. Jeff KingJul 6, 2024
  16. Jeff KingJul 6, 2024
  17. Eric SunshineJul 6, 2024
  18. Eric SunshineJul 6, 2024
  19. Jeff KingJul 6, 2024
  20. Eric SunshineJul 6, 2024
  21. Jeff KingJul 6, 2024
  22. 2/2 t: convert some here-doc test bodiesJeff King, Jul 1, 2024
  23. chainlint.pl: recognize test bodies defined via heredocEric Sunshine, Jul 2, 2024
  24. Jeff KingJul 6, 2024
  25. 1/3 chainlint.pl: fix line number reportingJeff King, Jul 6, 2024
  26. Eric SunshineJul 8, 2024
  27. Jeff KingJul 8, 2024
  28. 2/3 t/chainlint: add test_expect_success call to test snippetsJeff King, Jul 6, 2024
  29. Jeff KingJul 6, 2024
  30. Eric SunshineJul 8, 2024
  31. 3/3 t/chainlint: add tests for test body in heredocJeff King, Jul 6, 2024
  32. Eric SunshineJul 8, 2024
  33. Jeff KingJul 8, 2024
  34. Junio C HamanoJul 6, 2024
  35. Jeff KingJul 6, 2024
  36. Eric SunshineJul 8, 2024
  37. Jeff KingJul 8, 2024
  38. Eric SunshineJul 8, 2024
  39. Eric SunshineJul 8, 2024
  40. Jeff KingJul 10, 2024
  41. Jeff KingJul 10, 2024
  42. Eric SunshineJul 10, 2024
  43. Jeff KingJul 10, 2024
  44. Eric SunshineJul 10, 2024
  45. Eric SunshineJul 8, 2024
  46. Jeff KingJul 8, 2024
  47. Eric SunshineJul 8, 2024
  48. Jeff KingJul 10, 2024
  49. Eric SunshineJul 10, 2024
  50. 0/9 here-doc test bodies (now with 100% more chainlinting)Jeff King, Jul 10, 2024
  51. 1/9 chainlint.pl: add test_expect_success call to test snippetsJeff King, Jul 10, 2024
  52. 2/9 chainlint.pl: only start threads if jobs > 1Jeff King, Jul 10, 2024
  53. 3/9 chainlint.pl: do not spawn more threads than we have scriptsJeff King, Jul 10, 2024
  54. 4/9 chainlint.pl: force CRLF conversion when opening input filesJeff King, Jul 10, 2024
  55. 5/9 chainlint.pl: check line numbers in expected outputJeff King, Jul 10, 2024
  56. Eric SunshineAug 21, 2024
  57. Jeff KingAug 21, 2024
  58. Eric SunshineAug 21, 2024
  59. 6/9 chainlint.pl: recognize test bodies defined via heredocJeff King, Jul 10, 2024
  60. 7/9 chainlint.pl: add tests for test body in heredocJeff King, Jul 10, 2024
  61. 8/9 test-lib: allow test snippets as here-docsJeff King, Jul 10, 2024
  62. 9/9 t: convert some here-doc test bodiesJeff King, Jul 10, 2024
  63. 10/9 t/.gitattributes: ignore whitespace in chainlint expect filesJeff King, Jul 10, 2024
  64. Junio C HamanoJul 10, 2024
  65. Eric SunshineAug 21, 2024

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.