Home | History | Annotate | Line # | Download | only in sh
      1 # $NetBSD: t_here.sh,v 1.9 2021/11/22 05:21:54 kre Exp $
      2 #
      3 # Copyright (c) 2007 The NetBSD Foundation, Inc.
      4 # All rights reserved.
      5 #
      6 # Redistribution and use in source and binary forms, with or without
      7 # modification, are permitted provided that the following conditions
      8 # are met:
      9 # 1. Redistributions of source code must retain the above copyright
     10 #    notice, this list of conditions and the following disclaimer.
     11 # 2. Redistributions in binary form must reproduce the above copyright
     12 #    notice, this list of conditions and the following disclaimer in the
     13 #    documentation and/or other materials provided with the distribution.
     14 #
     15 # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     16 # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     17 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     18 # PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     19 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     20 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     21 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     22 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     23 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     24 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     25 # POSSIBILITY OF SUCH DAMAGE.
     26 #
     27 # the implementation of "sh" to test
     28 : ${TEST_SH:="/bin/sh"}
     29 
     30 nl='
     31 '
     32 
     33 reset()
     34 {
     35 	TEST_NUM=0
     36 	TEST_FAILURES=''
     37 	TEST_FAIL_COUNT=0
     38 	TEST_ID="$1"
     39 }
     40 
     41 check()
     42 {
     43 	fail=false
     44 	TEMP_FILE=$( mktemp OUT.XXXXXX )
     45 	TEST_NUM=$(( $TEST_NUM + 1 ))
     46 
     47 	# our local shell (ATF_SHELL) better do quoting correctly...
     48 	# some of the tests expect us to expand $nl internally...
     49 	CMD="nl='${nl}'; $1"
     50 
     51 	result="$( ${TEST_SH} -c "${CMD}" 2>"${TEMP_FILE}" )"
     52 	STATUS=$?
     53 
     54 	if [ "${STATUS}" -ne "$3" ]; then
     55 		echo >&2 "[$TEST_NUM] expected exit code $3, got ${STATUS}"
     56 
     57 		# don't actually fail just because of wrong exit code
     58 		# unless we either expected, or received "good"
     59 		case "$3/${STATUS}" in
     60 		(*/0|0/*) fail=true;;
     61 		esac
     62 	fi
     63 
     64 	if [ "$3" -eq 0 ]; then
     65 		if [ -s "${TEMP_FILE}" ]; then
     66 			echo >&2 \
     67 			 "[$TEST_NUM] Messages produced on stderr unexpected..."
     68 			cat "${TEMP_FILE}" >&2
     69 			fail=true
     70 		fi
     71 	else
     72 		if ! [ -s "${TEMP_FILE}" ]; then
     73 			echo >&2 \
     74 		    "[$TEST_NUM] Expected messages on stderr, nothing produced"
     75 			fail=true
     76 		fi
     77 	fi
     78 	rm -f "${TEMP_FILE}"
     79 
     80 	# Remove newlines (use local shell for this)
     81 	result="$(
     82 		IFS="$nl"
     83 		set -f
     84 		set -- $result
     85 		IFS=' '
     86 		printf %s "$*"
     87 	)"
     88 	if [ "$2" != "$result" ]
     89 	then
     90 		echo >&2 "[$TEST_NUM] Expected output '$2', received '$result'"
     91 		fail=true
     92 	fi
     93 
     94 	if $fail
     95 	then
     96 		echo >&2 "[$TEST_NUM] Full command: <<${CMD}>>"
     97 	fi
     98 
     99 	$fail && test -n "$TEST_ID" && {
    100 		TEST_FAILURES="${TEST_FAILURES}${TEST_FAILURES:+
    101 }${TEST_ID}[$TEST_NUM]: test of '$1' failed";
    102 		TEST_FAIL_COUNT=$(( $TEST_FAIL_COUNT + 1 ))
    103 		return 0
    104 	}
    105 	$fail && atf_fail "Test[$TEST_NUM] of '$1' failed"
    106 	return 0
    107 }
    108 
    109 results()
    110 {
    111 	test -z "${TEST_ID}" && return 0
    112 	test -z "${TEST_FAILURES}" && return 0
    113 
    114 	echo >&2 "=========================================="
    115 	echo >&2 "While testing '${TEST_ID}'"
    116 	echo >&2 " - - - - - - - - - - - - - - - - -"
    117 	echo >&2 "${TEST_FAILURES}"
    118 	atf_fail \
    119  "Test ${TEST_ID}: $TEST_FAIL_COUNT subtests (of $TEST_NUM) failed - see stderr"
    120 }
    121 
    122 atf_test_case do_simple
    123 do_simple_head() {
    124 	atf_set "descr" "Basic tests for here documents"
    125 }
    126 do_simple_body() {
    127 	y=x
    128 
    129 	reset 'simple'
    130 	IFS=' 	'
    131 	check 'x=`cat <<EOF'$nl'text'${nl}EOF$nl'`; echo $x' 'text' 0
    132 	check 'x=`cat <<\EOF'$nl'text'${nl}EOF$nl'`; echo $x' 'text' 0
    133 
    134 	check "y=${y};"'x=`cat <<EOF'$nl'te${y}t'${nl}EOF$nl'`; echo $x' \
    135 			'text' 0
    136 	check "y=${y};"'x=`cat <<\EOF'$nl'te${y}t'${nl}EOF$nl'`; echo $x'  \
    137 			'te${y}t' 0
    138 	check "y=${y};"'x=`cat <<"EOF"'$nl'te${y}t'${nl}EOF$nl'`; echo $x'  \
    139 			'te${y}t' 0
    140 	check "y=${y};"'x=`cat <<'"'EOF'"$nl'te${y}t'${nl}EOF$nl'`; echo $x'  \
    141 			'te${y}t' 0
    142 
    143 	# check that quotes in the here doc survive and cause no problems
    144 	check "cat <<EOF${nl}te'xt${nl}EOF$nl" "te'xt" 0
    145 	check "cat <<\EOF${nl}te'xt${nl}EOF$nl" "te'xt" 0
    146 	check "cat <<'EOF'${nl}te'xt${nl}EOF$nl" "te'xt" 0
    147 	check "cat <<EOF${nl}te\"xt${nl}EOF$nl" 'te"xt' 0
    148 	check "cat <<\EOF${nl}te\"xt${nl}EOF$nl" 'te"xt' 0
    149 	check "cat <<'EOF'${nl}te\"xt${nl}EOF$nl" 'te"xt' 0
    150 	check "cat <<'EO'F${nl}te\"xt${nl}EOF$nl" 'te"xt' 0
    151 
    152 	check "y=${y};"'x=`cat <<EOF'$nl'te'"'"'${y}t'${nl}EOF$nl'`; echo $x' \
    153 			'te'"'"'xt' 0
    154 	check "y=${y};"'x=`cat <<EOF'$nl'te'"''"'${y}t'${nl}EOF$nl'`; echo $x' \
    155 			'te'"''"'xt' 0
    156 
    157 	# note that the blocks of empty space in the following must
    158 	# be entirely tab characters, no spaces.
    159 
    160 	check 'x=`cat <<EOF'"$nl	text${nl}EOF$nl"'`; echo "$x"' \
    161 			'	text' 0
    162 	check 'x=`cat <<-EOF'"$nl	text${nl}EOF$nl"'`; echo $x' \
    163 			'text' 0
    164 	check 'x=`cat <<-EOF'"${nl}text${nl}	EOF$nl"'`; echo $x' \
    165 			'text' 0
    166 	check 'x=`cat <<-\EOF'"$nl	text${nl}	EOF$nl"'`; echo $x' \
    167 			'text' 0
    168 	check 'x=`cat <<- "EOF"'"$nl	text${nl}EOF$nl"'`; echo $x' \
    169 			'text' 0
    170 	check 'x=`cat <<- '"'EOF'${nl}text${nl}	EOF$nl"'`; echo $x' \
    171 			'text' 0
    172 	results
    173 }
    174 
    175 atf_test_case end_markers
    176 end_markers_head() {
    177 	atf_set "descr" "Tests for various end markers of here documents"
    178 }
    179 end_markers_body() {
    180 
    181 	reset 'end_markers'
    182 	for end in EOF 1 \! '$$$' "string " a\\\ a\\\ \   '&' '' ' ' '  ' \
    183 	    --STRING-- . '~~~' ')' '(' '#' '()' '(\)' '(\/)' '--' '\' '{' '}' \
    184 VERYVERYVERYVERYLONGLONGLONGin_fact_absurdly_LONG_LONG_HERE_DOCUMENT_TERMINATING_MARKER_THAT_goes_On_forever_and_ever_and_ever...
    185 	do
    186 		# check unquoted end markers
    187 		case "${end}" in
    188 		('' | *[' ()\$&#*~']* ) ;;	# skip unquoted endmark test for these
    189 		(*)	check \
    190 	'x=$(cat << '"${end}${nl}text${nl}${end}${nl}"'); printf %s "$x"' 'text' 0
    191 			;;
    192 		esac
    193 
    194 		# and quoted end markers
    195 		check \
    196 	'x=$(cat <<'"'${end}'${nl}text${nl}${end}${nl}"'); printf %s "$x"' 'text' 0
    197 
    198 		# and see what happens if we encounter "almost" an end marker
    199 		case "${#end}" in
    200 		(0|1)	;;		# too short to try truncation tests
    201 		(*)	check \
    202    'x=$(cat <<'"'${end}'${nl}text${nl}${end%?}${nl}${end}${nl}"'); printf %s "$x"' \
    203 				"text ${end%?}" 0
    204 			check \
    205    'x=$(cat <<'"'${end}'${nl}text${nl}${end#?}${nl}${end}${nl}"'); printf %s "$x"' \
    206 				"text ${end#?}" 0
    207 			check \
    208    'x=$(cat <<'"'${end}'${nl}text${nl}${end%?}+${nl}${end}${nl}"');printf %s "$x"' \
    209 				"text ${end%?}+" 0
    210 			;;
    211 		esac
    212 
    213 		# or something that is a little longer
    214 		check \
    215    'x=$(cat <<'"'${end}'${nl}text${nl}${end}x${nl}${end}${nl}"'); printf %s "$x"' \
    216 				"text ${end}x" 0
    217 		check \
    218     'x=$(cat <<'"'${end}'${nl}text${nl}!${end}${nl}${end}${nl}"'); printf %s "$x"' \
    219 				"text !${end}" 0
    220 
    221 		# or which does not begin at start of line
    222 		check \
    223     'x=$(cat <<'"'${end}'${nl}text${nl} ${end}${nl}${end}${nl}"'); printf %s "$x"' \
    224 				"text  ${end}" 0
    225 		check \
    226     'x=$(cat <<'"'${end}'${nl}text${nl}	${end}${nl}${end}${nl}"'); printf %s "$x"' \
    227 				"text 	${end}" 0
    228 
    229 		# or end at end of line
    230 		check \
    231     'x=$(cat <<'"'${end}'${nl}text${nl}${end} ${nl}${end}${nl}"'); printf %s "$x"' \
    232 				"text ${end} " 0
    233 
    234 		# or something that is correct much of the way, but then...
    235 
    236 		case "${#end}" in
    237 		(0)	;;		# cannot test this one
    238 		(1)	check \
    239     'x=$(cat <<'"'${end}'${nl}text${nl}${end}${end}${nl}${end}${nl}"'); printf %s "$x"' \
    240 				"text ${end}${end}" 0
    241 			;;
    242 		(2-7)	pfx="${end%?}"
    243 			check \
    244     'x=$(cat <<'"'${end}'${nl}text${nl}${end}${pfx}${nl}${end}${nl}"'); printf %s "$x"' \
    245 				"text ${end}${pfx}" 0
    246 			check \
    247     'x=$(cat <<'"'${end}'${nl}text${nl}${pfx}${end}${nl}${end}${nl}"'); printf %s "$x"' \
    248 				"text ${pfx}${end}" 0
    249 			;;
    250 		(*)	pfx=${end%??????}; sfx=${end#??????}
    251 			check \
    252     'x=$(cat <<'"'${end}'${nl}text${nl}${end}${sfx}${nl}${end}${nl}"'); printf %s "$x"' \
    253 				"text ${end}${sfx}" 0
    254 			check \
    255     'x=$(cat <<'"'${end}'${nl}text${nl}${pfx}${end}${nl}${end}${nl}"'); printf %s "$x"' \
    256 				"text ${pfx}${end}" 0
    257 			check \
    258     'x=$(cat <<'"'${end}'${nl}text${nl}${pfx}${sfx}${nl}${end}${nl}"'); printf %s "$x"' \
    259 				"text ${pfx}${sfx}" 0
    260 			;;
    261 		esac
    262 	done
    263 
    264 	# Add striptabs tests (in similar way) here one day...
    265 
    266 	results
    267 }
    268 
    269 atf_test_case incomplete
    270 incomplete_head() {
    271 	atf_set "descr" "Basic tests for incomplete here documents"
    272 }
    273 incomplete_body() {
    274 	reset incomplete
    275 
    276 	check 'cat <<EOF' '' 2
    277 	check 'cat <<- EOF' '' 2
    278 	check 'cat <<\EOF' '' 2
    279 	check 'cat <<- \EOF' '' 2
    280 
    281 	check 'cat <<EOF'"${nl}" '' 2
    282 	check 'cat <<- EOF'"${nl}" '' 2
    283 	check 'cat <<'"'EOF'${nl}" '' 2
    284 	check 'cat <<- "EOF"'"${nl}" '' 2
    285 
    286 	check 'cat << EOF'"${nl}${nl}" '' 2
    287 	check 'cat <<-EOF'"${nl}${nl}" '' 2
    288 	check 'cat << '"'EOF'${nl}${nl}" '' 2
    289 	check 'cat <<-"EOF"'"${nl}${nl}" '' 2
    290 
    291 	check 'cat << EOF'"${nl}"'line 1'"${nl}" '' 2
    292 	check 'cat <<-EOF'"${nl}"'	line 1'"${nl}" '' 2
    293 	check 'cat << EOF'"${nl}"'line 1'"${nl}"'	line 2'"${nl}" '' 2
    294 	check 'cat <<-EOF'"${nl}"'	line 1'"${nl}"'line 2'"${nl}" '' 2
    295 
    296 	check 'cat << EOF'"${nl}line 1${nl}${nl}line3${nl}${nl}5!${nl}" '' 2
    297 
    298 	results
    299 }
    300 
    301 atf_test_case lineends
    302 lineends_head() {
    303 	atf_set "descr" "Tests for line endings in here documents"
    304 }
    305 lineends_body() {
    306 	reset lineends
    307 
    308 	# note that "check" removes newlines from stdout before comparing.
    309 	# (they become blanks, provided there is something before & after)
    310 
    311 	check 'cat << \echo'"${nl}"'\'"${nl}echo${nl}echo${nl}" '\' 0
    312 	check 'cat <<  echo'"${nl}"'\'"${nl}echo${nl}echo${nl}" 'echo' 0
    313 	check 'cat << echo'"${nl}"'\\'"${nl}echo${nl}echo${nl}" '\' 0
    314 
    315 	check 'X=3; cat << ec\ho'"${nl}"'$X\'"${nl}echo${nl}echo${nl}" \
    316 		'$X\'  0
    317 	check 'X=3; cat <<  echo'"${nl}"'$X'"${nl}echo${nl}echo${nl}" \
    318 		'3'  0
    319 	check 'X=3; cat <<  echo'"${nl}"'$X\'"${nl}echo${nl}echo${nl}" \
    320 		''  0
    321 	check 'X=3; cat <<  echo'"${nl}"'${X}\'"${nl}echo${nl}echo${nl}" \
    322 		'3echo'  0
    323 	check 'X=3; cat <<  echo'"${nl}"'\$X\'"${nl}echo${nl}echo${nl}" \
    324 		'$Xecho'  0
    325 	check 'X=3; cat <<  echo'"${nl}"'\\$X \'"${nl}echo${nl}echo${nl}" \
    326 		'\3 echo'  0
    327 
    328 	check \
    329   'cat << "echo"'"${nl}"'line1\'"${nl}"'line2\'"${nl}echo${nl}echo${nl}" \
    330 		 'line1\ line2\'  0
    331 	check \
    332 	  'cat << echo'"${nl}"'line1\'"${nl}"'line2\'"${nl}echo${nl}echo${nl}" \
    333 	  'line1line2echo'  0
    334 
    335 	results
    336 }
    337 
    338 atf_test_case multiple
    339 multiple_head() {
    340 	atf_set "descr" "Tests for multiple here documents on one cmd line"
    341 }
    342 multiple_body() {
    343 	reset multiple
    344 
    345 	check \
    346     "(cat ; cat <&3) <<EOF0 3<<EOF3${nl}STDIN${nl}EOF0${nl}-3-${nl}EOF3${nl}" \
    347 		'STDIN -3-' 0
    348 
    349 	check "(read line; echo \"\$line\"; cat <<EOF1; echo \"\$line\") <<EOF2
    350 The File
    351 EOF1
    352 The Line
    353 EOF2
    354 "			'The Line The File The Line' 0
    355 
    356 	check "(read line; echo \"\$line\"; cat <<EOF; echo \"\$line\") <<EOF
    357 The File
    358 EOF
    359 The Line
    360 EOF
    361 "			'The Line The File The Line' 0
    362 
    363 	check "V=1; W=2; cat <<-1; cat <<2; cat <<- 3; cat <<'4';"' cat <<\5
    364 		$V
    365 		$W
    366 		3
    367 	4
    368 	5
    369 			1
    370 2
    371 	5
    372 					4*$W+\$V
    373 	3
    374 $W
    375 1
    376 2
    377 3
    378 4
    379 7+$V
    380 $W+6
    381 5
    382 '			'1 2 3 4 5 5 4*2+$V $W 1 2 3 7+$V $W+6'	0
    383 
    384 	results
    385 }
    386 
    387 atf_test_case nested
    388 nested_head() {
    389 	atf_set "descr" "Tests for nested here documents for one cmd"
    390 }
    391 nested_body() {
    392 	reset nested
    393 
    394 	check \
    395 'cat << EOF1'"${nl}"'$(cat << EOF2'"${nl}LINE${nl}EOF2${nl}"')'"${nl}EOF1${nl}"\
    396 	'LINE' 0
    397 
    398 # This next one fails ... and correctly, so we will omit it (bad test)
    399 # Reasoning is that the correct data "$(cat << EOF2)\nLINE\nEOF2\n" is
    400 # collected for the outer (EOF1) heredoc, when that is parsed, it looks
    401 # like
    402 #	$(cat <<EOF2)
    403 #	LINE
    404 #	EOF2
    405 # which looks like a good command - except it is being parsed in "heredoc"
    406 # syntax, which means it is enclosed in double quotes, which means that
    407 # the newline after the ')' in the first line is not a newline token, but
    408 # just a character.  The EOF2 heredoc cannot start until after the next
    409 # newline token, of which there are none here...  LINE and EOF2 are just
    410 # more data in the outer EOF1 heredoc for its "cat" command to read & write.
    411 #
    412 # The previous sub-test works because there the \n comes inside the
    413 # $( ), and in there, the outside quoting rules are suspended, and it
    414 # all starts again - so that \n is a newline token, and the EOF2 heredoc
    415 # is processed.
    416 #
    417 #	check \
    418 #   'cat << EOF1'"${nl}"'$(cat << EOF2 )'"${nl}LINE${nl}EOF2${nl}EOF1${nl}" \
    419 #	'LINE' 0
    420 
    421 	L='cat << EOF1'"${nl}"'LINE1$(cat << EOF2'"${nl}"
    422 	L="${L}"'LINE2$(cat << EOF3'"${nl}"
    423 	L="${L}"'LINE3$(cat << EOF4'"${nl}"
    424 	L="${L}"'LINE4$(cat << EOF5'"${nl}"
    425 	L="${L}LINE5${nl}EOF5${nl})4${nl}EOF4${nl})3${nl}"
    426 	L="${L}EOF3${nl})2${nl}EOF2${nl})1${nl}EOF1${nl}"
    427 
    428 	# That mess is ...
    429 	#
    430 	#	cat <<EOF1
    431 	#	LINE1$(cat << EOF2
    432 	#	LINE2$(cat << EOF3
    433 	#	LINE3$(cat << EOF4
    434 	#	LINE4$(cat << EOF5
    435 	#	LINE5
    436 	#	EOF5
    437 	#	)4
    438 	#	EOF4
    439 	#	)3
    440 	#	EOF3
    441 	#	)2
    442 	#	EOF2
    443 	#	)1
    444 	#	EOF1
    445 
    446 	check "${L}" 'LINE1LINE2LINE3LINE4LINE54321' 0
    447 
    448 	results
    449 }
    450 
    451 atf_test_case quoting
    452 quoting_head() {
    453 	atf_set "descr" "Tests for use of quotes inside here documents"
    454 }
    455 quoting_body() {
    456 	reset quoting
    457 
    458 	check 'X=!; cat <<- E\0F
    459 		<'\''"'\'' \\$X\$X  "'\''" \\>
    460 	E0F
    461 	'	'<'\''"'\'' \\$X\$X  "'\''" \\>'	0
    462 
    463 	check 'X=!; cat <<- E0F
    464 		<'\''"'\'' \\$X\$X  "'\''" \\>
    465 	E0F
    466 	'	'<'\''"'\'' \!$X  "'\''" \>'	0
    467 
    468 	check 'cat <<- END
    469 		$( echo "'\''" ) $( echo '\''"'\'' ) $( echo \\ )
    470 	END
    471 	'	"' \" \\"		0
    472 
    473 	check 'X=12345; Y="string1 line1?-line2"; Z=; unset W; cat <<-EOF
    474 		${#X}${Z:-${Y}}${W+junk}${Y%%l*}${Y#*\?}
    475 		"$Z"'\''$W'\'' ${Y%" "*} $(( X + 54321 ))
    476 	EOF
    477 	'	'5string1 line1?-line2string1 -line2 ""'\'\'' string1 66666' 0
    478 
    479 	# check that \ only quotes the magic chars, otherwise is retained
    480 	check 'p=A; cat <<-EOF
    481 		${p+\%$p\%}
    482 		${p+%$p%}
    483 	EOF
    484 	'	'\%A\% %A%' 0
    485 
    486 	# and check that " is not magic, so \ does not quote it
    487 	check 'p=A; cat <<-EOF
    488 		${p+\"$p\"}
    489 		${p+"$p"}
    490 	EOF
    491 	'	'\"A\" "A"' 0
    492 
    493 	# except in a ${var%<word>} word, base syntax reapplies, and
    494 	# there quotes are magic again
    495 	check 'p=ABCD; cat <<-EOF
    496 		${p%B?D}
    497 		${p%B\?D}
    498 		${p%"BCD"}
    499 		"${p%??}"
    500 		${p#"${p%??}"}
    501 		"${p#"${p%?"?"}"}"
    502 	EOF
    503 	'	'A ABCD A "AB" CD ""'	0
    504 
    505 	check 'p=AB??; cat <<-EOF
    506 		${p%B?D}
    507 		${p%B\??}
    508 		${p%"B??"}
    509 		"${p%??}"
    510 		${p#"${p%??}"}
    511 		"${p#"${p%?"?"}"}"
    512 	EOF
    513 	'	'AB?? A A "AB" ?? "??"'	0
    514 
    515 	results
    516 }
    517 
    518 #
    519 # This next test is really just testing what our shell happens to do.
    520 # There doesn't seem to be any spec on in which context expansions
    521 # in redirects are processed.   Most shells do them in the parent
    522 # shell context, meaning that side effects of the expansion become
    523 # visible to the shell - a couple process redirect expansions in
    524 # a subshell, meaning that side effects are lost.
    525 #
    526 # Before PR bin/53550 was fixed, the NetBSD sh evaluated all redirect
    527 # expansions, except here documents, in the context of the shell, and
    528 # here document redirects in a subshell context.   That distinction
    529 # makes no real sense (and only an old, and maybe still current, FreeBSD
    530 # shell shares that pecadillo.)   Afer that fix, the NetBSD shell joins
    531 # almost all others in expanding redirects (all of them) in the shell
    532 # context, meaning that side effects of here documenty expansions become
    533 # visible in the shell.
    534 #
    535 # Before the fix, we used to get "2\n1\n" as the output from this
    536 # test, now the variable assignment in the here document persists
    537 # and we get "2\n2\n" as do most other shells.  (bash is a notable
    538 # exception, but it does all redirect expansions in a subshell context)
    539 #
    540 
    541 atf_test_case side_effects
    542 side_effects_head() {
    543 	atf_set "descr" "Tests how side effects in here documents are handled"
    544 }
    545 side_effects_body() {
    546 
    547 	atf_check -s exit:0 -o inline:'2\n2\n' -e empty ${TEST_SH} -c '
    548 		unset X
    549 		cat <<-EOF
    550 		${X=2}
    551 		EOF
    552 		echo "${X-1}"
    553 		'
    554 }
    555 
    556 # This is a test for the specific bug reported in PR bin/53550
    557 # This should work in any shell.
    558 
    559 atf_test_case exit_status
    560 exit_status_head() {
    561 	atf_set descr "Tests exit status of a command substitution in a heredoc"
    562 }
    563 exit_status_body() {
    564 
    565 	# PR bin/53550 test
    566 	atf_check -s exit:7 -o empty -e empty ${TEST_SH} -c '
    567 		<<-EOF
    568 		$(exit 7)
    569 		EOF
    570 		'
    571 }
    572 
    573 # The following tests a problem reported on the austin-list 2021-09-08
    574 # by oguzismailuysal (at] gmail.com ... it affected all ash derived shells
    575 atf_test_case hard_cases
    576 hard_cases_head() {
    577 	atf_set "descr" \
    578 		"Tests here docs in positions that have confised our parser"
    579 }
    580 hard_cases_body() {
    581 
    582 	atf_check -s exit:0 -o inline:'xxx\n' -e empty ${TEST_SH} -c '
    583 		: <<- do | for x in xxx
    584 		do
    585 		do echo $x
    586 		done'
    587 
    588 	atf_check -s exit:0 -o inline:'xxx\n' -e empty ${TEST_SH} -c '
    589 		set -- xxx
    590 		: <<- done | for x in xxx
    591 		done
    592 		do echo $x
    593 		done'
    594 
    595 	atf_check -s exit:0 -o inline:'xxx\n' -e empty ${TEST_SH} -c '
    596 		: <<- in | case xxx
    597 		in
    598 		in xxx) echo xxx;;
    599 		esac'
    600 }
    601 
    602 atf_test_case vicious
    603 vicious_head() {
    604 	atf_set "descr" "Tests for obscure and obnoxious uses of here docs"
    605 }
    606 vicious_body() {
    607 	reset
    608 
    609 	cat <<- \END_SCRIPT > script
    610 		cat <<ONE && cat \
    611 		<<TWO
    612 		a
    613 		ONE
    614 		b
    615 		TWO
    616 	END_SCRIPT
    617 
    618 	atf_check -s exit:0 -o inline:'a\nb\n' -e empty ${TEST_SH} script
    619 
    620 	# This next one is causing discussion currently (late Feb 2016)
    621 	# amongst stds writers & implementors.   Consequently we
    622 	# will not check what it produces.   The eventual result
    623 	# seems unlikely to be what we currently output, which
    624 	# is:
    625 	#	A:echo line 1
    626 	#	B:echo line 2)" && prefix DASH_CODE <<DASH_CODE
    627 	#	B:echo line 3
    628 	#	line 4
    629 	#	line 5
    630 	#
    631 	# The likely intended output is ...
    632 	#
    633 	#	A:echo line 3
    634 	#	B:echo line 1
    635 	#	line 2
    636 	#	DASH_CODE:echo line 4)"
    637 	#	DASH_CODE:echo line 5
    638 	#
    639 	# The difference is explained by differing opinions on just
    640 	# when processing of a here doc should start
    641 
    642 	cat <<- \END_SCRIPT > script
    643 		prefix() { sed -e "s/^/$1:/"; }
    644 		DASH_CODE() { :; }
    645 
    646 		prefix A <<XXX && echo "$(prefix B <<XXX
    647 		echo line 1
    648 		XXX
    649 		echo line 2)" && prefix DASH_CODE <<DASH_CODE
    650 		echo line 3
    651 		XXX
    652 		echo line 4)"
    653 		echo line 5
    654 		DASH_CODE
    655 	END_SCRIPT
    656 
    657 	# we will just verify that the shell can parse the
    658 	# script somehow, and doesn't fall over completely...
    659 
    660 	atf_check -s exit:0 -o ignore -e empty ${TEST_SH} script
    661 }
    662 
    663 atf_init_test_cases() {
    664 	atf_add_test_case do_simple	# not worthy of a comment
    665 	atf_add_test_case end_markers	# the mundane, the weird, the bizarre
    666 	atf_add_test_case exit_status	# PR bin/53550, cmdsub in heredoc
    667 	atf_add_test_case incomplete	# where the end marker isn't...
    668 	atf_add_test_case lineends	# test weird line endings in heredocs
    669 	atf_add_test_case multiple	# multiple << operators on one cmd
    670 	atf_add_test_case nested	# here docs inside here docs
    671 	atf_add_test_case quoting	# stuff quoted inside
    672 	atf_add_test_case side_effects	# here docs that modify environment
    673 	atf_add_test_case hard_cases	# here doc bodies appearing mid command
    674 	atf_add_test_case vicious	# evil test from the austin-l list...
    675 }
    676