xref: /netbsd-src/tests/bin/sh/t_redir.sh (revision 2efc6dabcec1d044e97d752048d8545ab13d3ca0)
1# $NetBSD: t_redir.sh,v 1.14 2021/11/21 20:50:35 kre Exp $
2#
3# Copyright (c) 2016 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# Any failures in this first test means it is not worth bothering looking
31# for causes of failures in any other tests, make this one work first.
32
33# Problems with this test usually mean inadequate ATF_SHELL used for testing.
34# (though if all pass but the last, it might be a TEST_SH problem.)
35
36atf_test_case basic_test_method_test
37basic_test_method_test_head()
38{
39	atf_set "descr" "Tests that test method works as expected"
40}
41basic_test_method_test_body()
42{
43	cat <<- 'DONE' |
44	DONE
45	atf_check -s exit:0 -o empty -e empty ${TEST_SH} ||
46		atf_fail 'empty piped input'
47	cat <<- 'DONE' |
48	DONE
49	atf_check -s exit:0 -o match:0 -e empty ${TEST_SH} -c 'wc -l' ||
50		atf_fail 'empty piped input line count'
51
52	cat <<- 'DONE' |
53		echo hello
54	DONE
55	atf_check -s exit:0 -o match:hello -e empty ${TEST_SH}  ||
56		atf_fail 'piped hello'
57	cat <<- 'DONE' |
58		echo hello
59	DONE
60	atf_check -s exit:0 -o match:1 -e empty ${TEST_SH} -c 'wc -l' ||
61		atf_fail 'piped hello line count'
62
63	cat <<- 'DONE' |
64		echo hello\
65					world
66	DONE
67	atf_check -s exit:0 -o match:helloworld -e empty ${TEST_SH}  ||
68		atf_fail 'piped hello world'
69	cat <<- 'DONE' |
70		echo hello\
71					world
72	DONE
73	atf_check -s exit:0 -o match:2 -e empty ${TEST_SH} -c 'wc -l' ||
74		atf_fail 'piped hello world line check'
75
76	printf '%s\n%s\n%s\n' Line1 Line2 Line3 > File
77	atf_check -s exit:0 -o inline:'Line1\nLine2\nLine3\n' -e empty \
78		${TEST_SH} -c 'cat File'
79
80	cat <<- 'DONE' |
81		set -- X "" '' Y
82		echo ARGS="${#}"
83		echo '' -$1- -$2- -$3- -$4-
84		cat <<EOF
85			X=$1
86		EOF
87		cat <<\EOF
88			Y=$4
89		EOF
90	DONE
91	atf_check -s exit:0 -o match:ARGS=4 -o match:'-X- -- -- -Y-' \
92		-o match:X=X -o match:'Y=\$4' -e empty ${TEST_SH}  ||
93			atf_fail "complex piped input"
94
95	cat <<- 'DONE' |
96		echo expect to see a non-detected failure here
97	DONE
98	atf_check -s exit:1 -o empty -e empty ${TEST_SH} &&
99		atf_fail "Failed to fail as expected"
100
101	return 0
102}
103
104atf_test_case do_input_redirections
105do_input_redirections_head()
106{
107	atf_set "descr" "Tests that simple input redirection works"
108}
109do_input_redirections_body()
110{
111	printf '%s\n%s\n%s\nEND\n' 'First Line' 'Second Line' 'Line 3' >File
112
113	atf_check -s exit:0 -e empty \
114		-o inline:'First Line\nSecond Line\nLine 3\nEND\n' \
115		${TEST_SH} -c 'cat < File'
116	atf_check -s exit:0 -e empty \
117		-o inline:'First Line\nSecond Line\nLine 3\nEND\n' \
118		${TEST_SH} -c 'cat <File'
119	atf_check -s exit:0 -e empty \
120		-o inline:'First Line\nSecond Line\nLine 3\nEND\n' \
121		${TEST_SH} -c 'cat< File'
122	atf_check -s exit:0 -e empty \
123		-o inline:'First Line\nSecond Line\nLine 3\nEND\n' \
124		${TEST_SH} -c 'cat < "File"'
125	atf_check -s exit:0 -e empty \
126		-o inline:'First Line\nSecond Line\nLine 3\nEND\n' \
127		${TEST_SH} -c '< File cat'
128
129	ln File wc
130	atf_check -s exit:0 -e empty \
131		-o inline:'First Line\nSecond Line\nLine 3\nEND\n' \
132		${TEST_SH} -c '< wc cat'
133
134	mv wc cat
135	atf_check -s exit:0 -e empty -o match:4 \
136		${TEST_SH} -c '< cat wc'
137
138
139	cat <<- 'EOF' |
140		for l in 1 2 3; do
141			read line < File
142			echo "$line"
143		done
144	EOF
145	atf_check -s exit:0 -e empty \
146		-o inline:'First Line\nFirst Line\nFirst Line\n' \
147		${TEST_SH} ||
148			atf_fail 'loop rereading first line'
149
150	cat <<- 'EOF' |
151		for l in 1 2 3; do
152			read line
153			echo "$line"
154		done <File
155	EOF
156	atf_check -s exit:0 -e empty \
157		-o inline:'First Line\nSecond Line\nLine 3\n' \
158		${TEST_SH} ||
159			atf_fail 'loop reading file'
160
161	cat <<- 'EOF' |
162		for l in 1 2 3; do
163			read line < File
164			echo "$line"
165		done <File
166	EOF
167	atf_check -s exit:0 -e empty \
168		-o inline:'First Line\nFirst Line\nFirst Line\n' \
169		${TEST_SH} ||
170			atf_fail 'double redirect'
171
172	cat <<- 'EOF' |
173		line=
174		while [ "$line" != END ]; do
175			read line || exit 1
176			echo "$line"
177		done <File
178	EOF
179	atf_check -s exit:0 -e empty \
180		-o inline:'First Line\nSecond Line\nLine 3\nEND\n' \
181		${TEST_SH} ||
182			atf_fail 'read and test content'
183
184	cat <<- 'EOF' |
185		while :; do
186			read line || exit 0
187			echo "$line"
188		done <File
189	EOF
190	atf_check -s exit:0 -e empty \
191		-o inline:'First Line\nSecond Line\nLine 3\nEND\n' \
192		${TEST_SH} ||
193			atf_fail 'read and test status'
194
195	cat <<- 'EOF' |
196		l=''
197		while read line < File
198		do
199			echo "$line"
200			l="${l}x"
201			[ ${#l} -ge 3 ] && break
202		done
203		echo DONE
204	EOF
205	atf_check -s exit:0 -e empty \
206		-o inline:'First Line\nFirst Line\nFirst Line\nDONE\n' \
207		${TEST_SH} ||
208			atf_fail 'read 3 lines'
209
210	cat <<- 'EOF' |
211		while read line
212		do
213			echo "$line"
214		done <File
215		echo DONE
216	EOF
217	atf_check -s exit:0 -e empty \
218		-o inline:'First Line\nSecond Line\nLine 3\nEND\nDONE\n' \
219		${TEST_SH} ||
220			atf_fail 'read to EOF'
221
222	cat <<- 'EOF' |
223		l=''
224		while read line
225		do
226			echo "$line"
227			l="${l}x"
228			[ ${#l} -ge 3 ] && break
229		done <File
230		echo DONE
231	EOF
232	atf_check -s exit:0 -e empty \
233		-o inline:'First Line\nSecond Line\nLine 3\nDONE\n' \
234		${TEST_SH} ||
235			atf_fail 'read 3 and break'
236
237	cat <<- 'EOF' |
238		l=''
239		while read line1 <File
240		do
241			read line2
242			echo "$line1":"$line2"
243			l="${l}x"
244			[ ${#l} -ge 2 ] && break
245		done <File
246		echo DONE
247	EOF
248	atf_check -s exit:0 -e empty \
249	    -o inline:'First Line:First Line\nFirst Line:Second Line\nDONE\n' \
250		${TEST_SH} ||
251			atf_fail 'read and read again'
252}
253
254atf_test_case do_output_redirections
255do_output_redirections_head()
256{
257	atf_set "descr" "Test Output redirections"
258}
259do_output_redirections_body()
260{
261nl='
262'
263	T=0
264	i() { T=$(expr "$T" + 1); }
265
266	rm -f Output 2>/dev/null || :
267	test -f Output && atf_fail "Unable to remove Output file"
268#1
269	i; atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '> Output'
270	test -f Output || atf_fail "#$T: Did not make Output file"
271#2
272	rm -f Output 2>/dev/null || :
273	i; atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '>> Output'
274	test -f Output || atf_fail "#$T: Did not make Output file"
275#3
276	rm -f Output 2>/dev/null || :
277	i; atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '>| Output'
278	test -f Output || atf_fail "#$T: Did not make Output file"
279
280#4
281	rm -f Output 2>/dev/null || :
282	i
283	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c 'echo Hello >Output'
284	test -s Output || atf_fail "#$T: Did not make non-empty Output file"
285	test "$(cat Output)" = "Hello" ||
286	  atf_fail "#$T: Incorrect Output: Should be 'Hello' is '$(cat Output)'"
287#5
288	i
289	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c 'echo Hello>!Output'
290	test -s Output || atf_fail "#$T: Did not make non-empty Output file"
291	test "$(cat Output)" = "Hello" ||
292	  atf_fail "#$T: Incorrect Output: Should be 'Hello' is '$(cat Output)'"
293#6
294	i
295	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c 'echo Bye >>Output'
296	test -s Output || atf_fail "#$T: Removed Output file"
297	test "$(cat Output)" = "Hello${nl}Bye" || atf_fail \
298	  "#$T: Incorrect Output: Should be 'Hello\\nBye' is '$(cat Output)'"
299#7
300	i; atf_check -s exit:0 -o inline:'line 1\nline 2\n' -e empty \
301		${TEST_SH} -c \
302		'echo line 1 > Output; echo line 2 >> Output; cat Output'
303	test "$(cat Output)" = "line 1${nl}line 2" || atf_fail \
304	 "#$T: Incorrect Output: Should be 'line 1\\nline 2' is '$(cat Output)'"
305#8
306	i; atf_check -s exit:0 -o inline:'line 2\n' -e empty \
307		${TEST_SH} -c 'echo line 1 > Output; echo line 2'
308	test "$(cat Output)" = "line 1" || atf_fail \
309	    "#$T: Incorrect Output: Should be 'line 1' is '$(cat Output)'"
310#9
311	i; atf_check -s exit:0 -o empty -e empty \
312		${TEST_SH} -c '(echo line 1; echo line 2 > Out2) > Out1'
313	test "$(cat Out1)" = "line 1" || atf_fail \
314	    "#$T: Incorrect Out1: Should be 'line 1' is '$(cat Out1)'"
315	test "$(cat Out2)" = "line 2" || atf_fail \
316	    "#$T: Incorrect Out2: Should be 'line 2' is '$(cat Out2)'"
317#10
318	i; atf_check -s exit:0 -o empty -e empty \
319		${TEST_SH} -c '{ echo line 1; echo line 2 > Out2;} > Out1'
320	test "$(cat Out1)" = "line 1" || atf_fail \
321	    "#$T: Incorrect Out1: Should be 'line 1' is '$(cat Out1)'"
322	test "$(cat Out2)" = "line 2" || atf_fail \
323	    "#$T: Incorrect Out2: Should be 'line 2' is '$(cat Out2)'"
324#11
325	i; rm -f Out1 Out2 2>/dev/null || :
326	cat <<- 'EOF' |
327		for arg in 'line 1' 'line 2' 'line 3'
328		do
329			echo "$arg"
330			echo "$arg" > Out1
331		done > Out2
332	EOF
333	atf_check -s exit:0 -o empty -e empty ${TEST_SH}  ||
334		atf_fail "#$T:  not empty/empty/0"
335	test "$(cat Out1)" = "line 3" || atf_fail \
336		"#$T:  Incorrect Out1: Should be 'line 3' is '$(cat Out1)'"
337	test "$(cat Out2)" = "line 1${nl}line 2${nl}line 3" || atf_fail \
338    "#$T: Incorrect Out2: Should be 'line 1\\nline 2\\nline 3' is '$(cat Out2)'"
339#12
340	i; rm -f Out1 Out2 2>/dev/null || :
341	cat <<- 'EOF' |
342		for arg in 'line 1' 'line 2' 'line 3'
343		do
344			echo "$arg"
345			echo "$arg" >> Out1
346		done > Out2
347	EOF
348	atf_check -s exit:0 -o empty -e empty ${TEST_SH}  ||
349		atf_fail "#$T:  not empty/empty/0"
350	test "$(cat Out1)" = "line 1${nl}line 2${nl}line 3" || atf_fail \
351    "#$T: Incorrect Out1: Should be 'line 1\\nline 2\\nline 3' is '$(cat Out1)'"
352	test "$(cat Out2)" = "line 1${nl}line 2${nl}line 3" || atf_fail \
353    "#$T: Incorrect Out2: Should be 'line 1\\nline 2\\nline 3' is '$(cat Out2)'"
354}
355
356atf_test_case do_redirect_input_output
357do_redirect_input_output_head()
358{
359	atf_set "descr" "Test Input+Output (BiDir) redirections"
360}
361do_redirect_input_output_body()
362{
363nl='
364'
365	T=0
366	i() { T=$(expr "$T" + 1); }
367
368	rm -f Output 2>/dev/null || :
369	test -f Output && atf_fail "Unable to remove Output file"
370#1
371	i; atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '<> Output'
372	test -f Output || atf_fail "#$T: Did not make Output file"
373
374#2
375	echo data >Output 2>/dev/null || :
376	i
377	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
378		'<>Output'
379	test -f Output || atf_fail "#$T: Removed Output file"
380	test -s Output || atf_fail "#$T: Did not keep data in Output file"
381	test "$(cat Output)" = "data" ||
382	  atf_fail "#$T: Incorrect Output: Should be 'data' is '$(cat Output)'"
383
384#3
385	rm -f Output 2>/dev/null || :
386	i
387	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
388		'echo Hello 1<>Output'
389	test -s Output || atf_fail "#$T: Did not keep non-empty Output file"
390	test "$(cat Output)" = "Hello" ||
391	  atf_fail "#$T: Incorrect Output: Should be 'Hello' is '$(cat Output)'"
392
393#4
394	printf data >Output 2>/dev/null || :
395	i
396	atf_check -s exit:0 -o inline:'data' -e empty ${TEST_SH} -c \
397		'cat <>Output'
398	test -f Output || atf_fail "#$T: Removed Output file"
399	test -s Output || atf_fail "#$T: Did not keep data in Output file"
400	test "$(cat Output)" = "data" ||
401	  atf_fail "#$T: Incorrect Output: Should be 'data' is '$(cat Output)'"
402
403#5
404	echo data >Output 2>/dev/null || :
405	i
406	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
407		'echo Hello 1<>Output'
408	test -s Output || atf_fail "#$T: Did not make non-empty Output file"
409	test "$(cat Output)" = "Hello" ||
410	  atf_fail "#$T: Incorrect Output: Should be 'Hello' is '$(cat Output)'"
411
412#6
413	printf data >Output 2>/dev/null || :
414	i
415	atf_check -s exit:0 -o inline:data -e empty ${TEST_SH} -c \
416		'{ cat >&3; printf file; } <>Output 3>&1 >&0'
417	test -f Output || atf_fail "#$T: Removed Output file"
418	test -s Output || atf_fail "#$T: Did not keep data in Output file"
419	test "$(cat Output)" = "datafile" ||
420	  atf_fail \
421	      "#$T: Incorrect Output: Should be 'datafile' is '$(cat Output)'"
422}
423
424atf_test_case fd_redirections
425fd_redirections_head()
426{
427	atf_set "descr" "Tests redirections to/from specific descriptors"
428}
429fd_redirections_body()
430{
431	atf_require_prog /bin/echo
432
433	cat <<- 'DONE' > helper.sh
434		f() {
435			/bin/echo nothing "$1" >& "$1"
436		}
437		for n
438		do
439			eval "f $n $n"'> file-$n'
440		done
441	DONE
442	cat <<- 'DONE' > reread.sh
443		f() {
444			(read -r var; echo "${var}") <&"$1"
445		}
446		for n
447		do
448			x=$( eval "f $n $n"'< file-$n' )
449			test "${x}" = "nothing $n" || echo "$n"
450		done
451	DONE
452
453	validate()
454	{
455	    for n
456	    do
457		test -e "file-$n" || atf_fail "file-$n not created"
458		C=$(cat file-"$n")
459		test "$C" = "nothing $n" ||
460			atf_fail "file-$n contains '$C' not 'nothing $n'"
461	    done
462	}
463
464	atf_check -s exit:0 -e empty -o empty \
465		${TEST_SH} helper.sh 1 2 3 4 5 6 7 8 9
466	validate 1 2 3 4 5 6 7 8 9
467	atf_check -s exit:0 -e empty -o empty \
468		${TEST_SH} reread.sh 3 4 5 6 7 8 9
469
470	L=$(ulimit -n)
471	if [ "$L" -ge 30 ]
472	then
473		atf_check -s exit:0 -e empty -o empty \
474			${TEST_SH} helper.sh 10 15 19 20 25 29
475		validate 10 15 19 20 25 29
476		atf_check -s exit:0 -e empty -o empty \
477			${TEST_SH} reread.sh 10 15 19 20 25 29
478	fi
479	if [ "$L" -ge 100 ]
480	then
481		atf_check -s exit:0 -e empty -o empty \
482			${TEST_SH} helper.sh 32 33 49 50 51 63 64 65 77 88 99
483		validate 32 33 49 50 51 63 64 65 77 88 99
484		atf_check -s exit:0 -e empty -o empty \
485			${TEST_SH} reread.sh 32 33 49 50 51 63 64 65 77 88 99
486	fi
487	if [ "$L" -ge 500 ]
488	then
489		atf_check -s exit:0 -e empty -o empty \
490			${TEST_SH} helper.sh 100 101 199 200 222 333 444 499
491		validate 100 101 199 200 222 333 444 499
492		atf_check -s exit:0 -e empty -o empty \
493			${TEST_SH} reread.sh 100 101 199 200 222 333 444 499
494	fi
495	if [ "$L" -gt 1005 ]
496	then
497		atf_check -s exit:0 -e empty -o empty \
498			${TEST_SH} helper.sh 1000 1001 1002 1003 1004 1005
499		validate 1000 1001 1002 1003 1004 1005
500		atf_check -s exit:0 -e empty -o empty \
501			${TEST_SH} reread.sh 1000 1001 1002 1003 1004 1005
502	fi
503}
504
505atf_test_case local_redirections
506local_redirections_head()
507{
508	atf_set "descr" \
509	    "Tests that exec can reassign file descriptors in the shell itself"
510}
511local_redirections_body()
512{
513	cat <<- 'DONE' > helper.sh
514		for f
515		do
516			eval "exec $f"'> file-$f'
517		done
518
519		for f
520		do
521			printf '%s\n' "Hello $f" >&"$f"
522		done
523
524		for f
525		do
526			eval "exec $f"'>&-'
527		done
528
529		for f
530		do
531			eval "exec $f"'< file-$f'
532		done
533
534		for f
535		do
536			exec <& "$f"
537			read -r var || echo >&2 "No data in file-$f"
538			read -r x && echo >&2 "Too much data in file-${f}: $x"
539			test "${var}" = "Hello $f" ||
540			    echo >&2 "file-$f contains '${var}' not 'Hello $f'"
541		done
542	DONE
543
544	atf_check -s exit:0 -o empty -e empty \
545		${TEST_SH} helper.sh 3 4 5 6 7 8 9
546
547	L=$(ulimit -n)
548	if [ "$L" -ge 30 ]
549	then
550		atf_check -s exit:0 -o empty -e empty \
551			${TEST_SH} helper.sh 10 11 13 15 16 19 20 28 29
552	fi
553	if [ "$L" -ge 100 ]
554	then
555		atf_check -s exit:0 -o empty -e empty \
556			${TEST_SH} helper.sh 30 31 32 63 64 65 77 88 99
557	fi
558	if [ "$L" -ge 500 ]
559	then
560		atf_check -s exit:0 -o empty -e empty \
561			${TEST_SH} helper.sh 100 101 111 199 200 201 222 333 499
562	fi
563	if [ "$L" -ge 1005 ]
564	then
565		atf_check -s exit:0 -o empty -e empty \
566			${TEST_SH} helper.sh 1000 1001 1002 1003 1004 1005
567	fi
568}
569
570atf_test_case named_fd_redirections
571named_fd_redirections_head()
572{
573	atf_set "descr" "Tests redirections to /dev/stdout (etc)"
574
575}
576named_fd_redirections_body()
577{
578	if test -c /dev/stdout
579	then
580		atf_check -s exit:0 -o inline:'OK\n' -e empty \
581			${TEST_SH} -c 'echo OK >/dev/stdout'
582		atf_check -s exit:0 -o inline:'OK\n' -e empty \
583			${TEST_SH} -c '/bin/echo OK >/dev/stdout'
584	fi
585
586	if test -c /dev/stdin
587	then
588		atf_require_prog cat
589
590		echo GOOD | atf_check -s exit:0 -o inline:'GOOD\n' -e empty \
591			${TEST_SH} -c 'read var </dev/stdin; echo $var' ||
592				atf_fail "/dev/stdin test 1"
593		echo GOOD | atf_check -s exit:0 -o inline:'GOOD\n' -e empty \
594			${TEST_SH} -c 'cat </dev/stdin' ||
595				atf_fail "/dev/stdin test 2"
596	fi
597
598	if test -c /dev/stderr
599	then
600		atf_check -s exit:0 -e inline:'OK\n' -o empty \
601			${TEST_SH} -c 'echo OK 2>/dev/stderr >&2'
602		atf_check -s exit:0 -e inline:'OK\n' -o empty \
603			${TEST_SH} -c '/bin/echo OK 2>/dev/stderr >&2'
604	fi
605
606	if test -c /dev/fd/8 && test -c /dev/fd/9
607	then
608		atf_check -s exit:0 -o inline:'EIGHT\n' -e empty \
609			${TEST_SH} -c 'printf "%s\n" EIGHT 8>&1 >/dev/fd/8 |
610					cat 9<&0 </dev/fd/9'
611	fi
612
613	return 0
614}
615
616atf_test_case redir_in_case
617redir_in_case_head()
618{
619	atf_set "descr" "Tests that sh(1) allows just redirections " \
620	                "in case statements. (PR bin/48631)"
621}
622redir_in_case_body()
623{
624	atf_check -s exit:0 -o empty -e empty \
625	    ${TEST_SH} -c 'case x in (whatever) >foo;; esac'
626
627	atf_check -s exit:0 -o empty -e empty \
628	    ${TEST_SH} -c 'case x in (whatever) >foo 2>&1;; esac'
629
630	atf_check -s exit:0 -o empty -e empty \
631	    ${TEST_SH} -c 'case x in (whatever) >foo 2>&1 </dev/null;; esac'
632
633	atf_check -s exit:0 -o empty -e empty \
634	    ${TEST_SH} -c 'case x in (whatever) >${somewhere};; esac'
635}
636
637atf_test_case incorrect_redirections
638incorrect_redirections_head()
639{
640	atf_set "descr" "Tests that sh(1) correctly ignores non-redirections"
641}
642incorrect_redirections_body() {
643
644	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c 'echo foo>'
645	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c 'read foo<'
646	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c 'echo foo<>'
647	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \
648		'echo x > '"$nl"
649	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \
650		'read x < '"$nl"
651	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \
652		'echo x <> '"$nl"
653	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \
654		'echo x >< anything'
655	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \
656		'echo x >>< anything'
657	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \
658		'echo x >|< anything'
659	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \
660		'echo x > ; read x < /dev/null || echo bad'
661	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \
662		'read x < & echo y > /dev/null; wait && echo bad'
663
664	rm -f Output 2>/dev/null || :
665	atf_check -s exit:0 -e empty -o inline:'A Line > Output\n' \
666		${TEST_SH} -c 'echo A Line \> Output'
667	test -f Output && atf_file "File 'Output' appeared and should not have"
668
669	rm -f Output 2>/dev/null || :
670	atf_check -s exit:0 -e empty -o empty \
671		${TEST_SH} -c 'echo A Line \>> Output'
672	test -f Output || atf_file "File 'Output' not created when it should"
673	test "$(cat Output)" = 'A Line >' || atf_fail \
674		"Output file contains '$(cat Output)' instead of '"'A Line >'\'
675
676	rm -f Output \> 2>/dev/null || :
677	atf_check -s exit:0 -e empty -o empty \
678		${TEST_SH} -c 'echo A Line >\> Output'
679	test -f Output && atf_file "File 'Output' appeared and should not have"
680	test -f '>' || atf_file "File '>' not created when it should"
681	test "$(cat '>')" = 'A Line Output' || atf_fail \
682	    "Output file ('>') contains '$(cat '>')' instead of 'A Line Output'"
683
684	rm -fr OutDir
685	atf-check -s not-exit:0 -o empty -e not-empty \
686		${TEST_SH} -c ': > OutDir/stdout; printf foo'
687	atf-check -s not-exit:0 -o empty -e not-empty \
688		${TEST_SH} -c ': > OutDir/stdout || printf foo; printf bar'
689	atf-check -s exit:0 -o inline:bar -e not-empty \
690		${TEST_SH} -c '> OutDir/stdout; printf bar'
691	atf-check -s exit:0 -o inline:foobar -e not-empty \
692		${TEST_SH} -c '> OutDir/stdout || printf foo; printf bar'
693	atf-check -s exit:0 -o inline:bar -e not-empty \
694		${TEST_SH} -c 'command : > OutDir/stdout; printf bar'
695	atf-check -s exit:0 -o inline:foobar -e not-empty ${TEST_SH} -c \
696		'command : > OutDir/stdout || printf foo; printf bar'
697	atf-check -s not-exit:0 -o empty -e not-empty \
698		${TEST_SH} -c ': <> OutDir/stdout; printf foo'
699
700	atf-check -s not-exit:0 -o empty -e not-empty \
701		${TEST_SH} -c ': >&8 ; printf foo'
702	atf-check -s not-exit:0 -o empty -e not-empty \
703		${TEST_SH} -c ': >&8 || printf foo; printf bar'
704	atf-check -s exit:0 -o inline:bar -e not-empty \
705		${TEST_SH} -c '>&8 ; printf bar'
706	atf-check -s exit:0 -o inline:foobar -e not-empty \
707		${TEST_SH} -c '>&8 || printf foo; printf bar'
708	atf-check -s exit:0 -o inline:bar -e not-empty \
709		${TEST_SH} -c 'command : >&7; printf bar'
710	atf-check -s exit:0 -o inline:foobar -e not-empty ${TEST_SH} -c \
711		'command : >&7 || printf foo; printf bar'
712
713	return 0
714}
715
716# Many more tests in t_here, so here we have just rudimentary checks
717atf_test_case redir_here_doc
718redir_here_doc_head()
719{
720	atf_set "descr" "Tests that sh(1) correctly processes 'here' doc " \
721	                "input redirections"
722}
723redir_here_doc_body()
724{
725	# nb: the printf is not executed, it is data
726	cat <<- 'DONE' |
727		cat <<EOF
728			printf '%s\n' 'hello\n'
729		EOF
730	DONE
731	atf_check -s exit:0 -o match:printf -o match:'hello\\n' \
732		-e empty ${TEST_SH}  ||
733			atf_fail "redir_here_doc failed"
734}
735
736atf_test_case subshell_redirections
737subshell_redirections_head()
738{
739	atf_set "descr" "Tests redirection interactions between shell and " \
740			"its sub-shell(s)"
741}
742subshell_redirections_body()
743{
744	atf_require_prog cat
745
746	LIM=$(ulimit -n)
747
748	cat <<- 'DONE' |
749		exec 6>output-file
750
751		( printf "hello\n" >&6 )
752
753		exec 8<output-file
754
755		( read hello <&8 ; test hello = "$hello" || echo >&2 Hello )
756
757		( printf "bye-bye\n" >&6 )
758
759		( exec 8<&- )
760		read bye <&8 || echo >&2 "Closed?"
761		echo Bye="$bye"
762	DONE
763	atf_check -s exit:0 -o match:Bye=bye-bye -e empty \
764		${TEST_SH} ||
765			atf_fail 'bye-bye failure'
766
767	cat <<- 'DONE' |
768		for arg in one-4 two-24 three-14
769		do
770			fd=${arg#*-}
771			file=${arg%-*}
772			eval "exec ${fd}>${file}"
773		done
774
775		for arg in one-5 two-7 three-19
776		do
777			fd=${arg#*-}
778			file=${arg%-*}
779			eval "exec ${fd}<${file}"
780		done
781
782		(
783			echo line-1 >&4
784			echo line-2 >&24
785			echo line-3 >&14
786			echo go
787		) | (
788			read go
789			read x <&5
790			read y <&7
791			read z <&19
792
793			printf "%s\n" "${x}" "${y}" "${z}"
794		)
795	DONE
796	atf_check -s exit:0 -o inline:'line-1\nline-2\nline-3\n' \
797		-e empty ${TEST_SH} ||
798			atf_fail 'complex 3 line redirects'
799
800	cat <<- 'DONE' |
801		for arg in one-4-5 two-6-7 three-8-9 four-11-10 five-3-12
802		do
803			ofd=${arg##*-}
804			file=${arg%-*}
805			ifd=${file#*-}
806			file=${file%-*}
807			eval "exec ${ofd}>${file}"
808			eval "exec ${ifd}<${file}"
809		done
810
811		( ( ( echo line-1 >& 13 ) 13>&12 ) 12>&5 ) >stdout 2>errout
812		( ( ( echo line-2 >& 4) 13>&12 ) 4>&7 ) >>stdout 2>>errout
813		( ( ( echo line-3 >& 6) 8>&1 6>&11 >&12) 11>&9 >&7 ) >>stdout
814
815		( ( ( cat <&13 >&12 ) 13<&8 12>&10 ) 10>&1 8<&6 ) 6<&4
816		( ( ( cat <&4 ) <&4 6<&8 8<&11  )
817			<&4 4<&6 6<&8 8<&11 ) <&4 4<&6 6<&8 8<&11 11<&3
818		( ( ( cat <&7 >&1 ) 7<&6 >&10 ) 10>&2 6<&8 ) 2>&1
819	DONE
820	atf_check -s exit:0 -o inline:'line-1\nline-2\nline-3\n' \
821		-e empty ${TEST_SH} ||
822			atf_fail 'absurd 3 line redirects'
823}
824
825atf_test_case ulimit_redirection_interaction
826ulimit_redirection_interaction_head()
827{
828	atf_set "descr" "Tests interactions between redirect and ulimit -n "
829}
830ulimit_redirection_interaction_body()
831{
832	atf_require_prog ls
833
834	cat <<- 'DONE' > helper.sh
835		oLIM=$(ulimit -n)
836		HRD=$(ulimit -H -n)
837		test "${oLIM}" -lt "${HRD}"  && ulimit -n "${HRD}"
838		LIM=$(ulimit -n)
839
840		FDs=
841		LFD=-1
842		while [ ${LIM} -gt 16 ]
843		do
844			FD=$(( ${LIM} - 1 ))
845			if [ "${FD}" -eq "${LFD}" ]; then
846				echo >&2 "Infinite loop... (busted $(( )) ??)"
847				exit 1
848			fi
849			LFD="${FD}"
850
851			eval "exec ${FD}"'> /dev/null'
852			FDs="${FD}${FDs:+ }${FDs}"
853
854			(
855				FD=$(( ${LIM} + 1 ))
856				eval "exec ${FD}"'> /dev/null'
857				echo "Reached unreachable command"
858			) 2>/dev/null && echo >&2 "Opened beyond limit!"
859
860			(eval 'ls 2>&1 3>&1 4>&1 5>&1 '"${FD}"'>&1') >&"${FD}"
861
862			LIM=$(( ${LIM} / 2 ))
863			ulimit -S -n "${LIM}"
864		done
865
866		# Even though ulimit has been reduced, open fds should work
867		for FD in ${FDs}
868		do
869			echo ${FD} in ${FDs} >&"${FD}" || exit 1
870		done
871
872		ulimit -S -n "${oLIM}"
873
874		# maybe more later...
875
876	DONE
877
878	atf_check -s exit:0 -o empty -e empty ${TEST_SH} helper.sh
879}
880
881atf_test_case validate_fn_redirects
882validate_fn_redirects_head()
883{
884	# These test cases inspired by PR bin/48875 and the sh
885	# changes that were required to fix it.
886
887	atf_set "descr" "Tests various redirections applied to functions " \
888		"See PR bin/48875"
889}
890validate_fn_redirects_body()
891{
892	cat <<- 'DONE' > f-def
893		f() {
894			printf '%s\n' In-Func
895		}
896	DONE
897
898	atf_check -s exit:0 -o inline:'In-Func\nsuccess1\n' -e empty \
899		${TEST_SH} -c ". ./f-def; f ; printf '%s\n' success1"
900	atf_check -s exit:0 -o inline:'success2\n' -e empty \
901		${TEST_SH} -c ". ./f-def; f >/dev/null; printf '%s\n' success2"
902	atf_check -s exit:0 -o inline:'success3\n' -e not-empty \
903		${TEST_SH} -c ". ./f-def; f >&- ; printf '%s\n' success3"
904	atf_check -s exit:0 -o inline:'In-Func\nsuccess4\n' -e empty \
905		${TEST_SH} -c ". ./f-def; f & wait; printf '%s\n' success4"
906	atf_check -s exit:0 -o inline:'success5\n' -e not-empty \
907		${TEST_SH} -c ". ./f-def; f >&- & wait; printf '%s\n' success5"
908	atf_check -s exit:0 -o inline:'In-Func\nIn-Func\nsuccess6\n' -e empty \
909		${TEST_SH} -c ". ./f-def; f;f; printf '%s\n' success6"
910	atf_check -s exit:0 -o inline:'In-Func\nIn-Func\nsuccess7\n' -e empty \
911		${TEST_SH} -c ". ./f-def; { f;f;}; printf '%s\n' success7"
912	atf_check -s exit:0 -o inline:'In-Func\nIn-Func\nsuccess8\n' -e empty \
913		${TEST_SH} -c ". ./f-def; { f;f;}& wait; printf '%s\n' success8"
914	atf_check -s exit:0 -o inline:'In-Func\nsuccess9\n' -e empty \
915		${TEST_SH} -c \
916		   ". ./f-def; { f>/dev/null;f;}& wait; printf '%s\n' success9"
917	atf_check -s exit:0 -o inline:'In-Func\nsuccess10\n' -e empty \
918		${TEST_SH} -c \
919		   ". ./f-def; { f;f>/dev/null;}& wait; printf '%s\n' success10"
920
921	# This one tests the issue etcupdate had with the original 48875 fix
922	atf_check -s exit:0 -o inline:'Func a\nFunc b\nFunc c\n' -e empty \
923		${TEST_SH} -c '
924			f() {
925				echo Func "$1"
926			}
927			exec 3<&0 4>&1
928			( echo x-a; echo y-b; echo z-c ) |
929			while read A
930			do
931				B=${A#?-}
932				f "$B" <&3 >&4
933			done >&2'
934
935	# And this tests a similar condition with that same fix
936	cat  <<- 'DONE' >Script
937		f() {
938			printf '%s' " hello $1"
939		}
940		exec 3>&1
941		echo $( for i in a b c
942			do printf '%s' @$i; f $i >&3; done >foo
943		)
944		printf '%s\n' foo=$(cat foo)
945	DONE
946	atf_check -s exit:0 -e empty \
947	    -o inline:' hello a hello b hello c\nfoo=@a@b@c\n' \
948	    ${TEST_SH} Script
949
950	# Tests with sh reading stdin, which is not quite the same internal
951	# mechanism.
952	echo ". ./f-def || echo >&2 FAIL
953		f
954		printf '%s\n' stdin1
955	" | atf_check -s exit:0 -o inline:'In-Func\nstdin1\n' -e empty \
956	      ${TEST_SH} ||
957		atf_fail "stdin1 test failure"
958
959	echo '
960		. ./f-def || echo >&2 FAIL
961		f >&- 2>/dev/null
962		printf "%s\n" stdin2
963	' | atf_check -s exit:0 -o inline:'stdin2\n' -e empty ${TEST_SH} ||
964		atf_fail "stdin2 test failure"
965
966	cat <<- 'DONE' > fgh.def
967		f() {
968			echo -n f >&3
969			sleep 4
970			echo -n F >&3
971		}
972		g() {
973			echo -n g >&3
974			sleep 2
975			echo -n G >&3
976		}
977		h() {
978			echo -n h >&3
979		}
980	DONE
981
982	atf_check -s exit:0 -o inline:'fFgGh' -e empty \
983		${TEST_SH} -c '. ./fgh.def || echo >&2 FAIL
984			exec 3>&1
985			f; g; h'
986
987	atf_check -s exit:0 -o inline:'fghGF' -e empty \
988		${TEST_SH} -c '. ./fgh.def || echo >&2 FAIL
989			exec 3>&1
990			f & sleep 1; g & sleep 1; h; wait'
991
992	atf_check -s exit:0 -o inline:'fFgGhX Y\n' -e empty \
993		${TEST_SH} -c '. ./fgh.def || echo >&2 FAIL
994			exec 3>&1
995			echo X $( f ; g ; h ) Y'
996
997	# This one is the real test for PR bin/48875.  If the
998	# cmdsub does not complete before f g (and h) exit,
999	# then the 'F' & 'G' will precede 'X Y' in the output.
1000	# If the cmdsub finishes while f & g are still running,
1001	# then the X Y will appear before the F and G.
1002	# The trailing "sleep 3" is just so we catch all the
1003	# output (otherwise atf_check will be finished while
1004	# f & g are still sleeping).
1005
1006	atf_check -s exit:0 -o inline:'fghX Y\nGF' -e empty \
1007		${TEST_SH} -c '. ./fgh.def || echo >&2 FAIL
1008			exec 3>&1
1009			echo X $( f >&- & sleep 1; g >&- & sleep 1 ; h ) Y
1010			sleep 3
1011			exec 4>&1 || echo FD_FAIL
1012			'
1013
1014	# Do the test again to verify it also all works reading stdin
1015	# (which is a slightly different path through the shell)
1016	echo '
1017		. ./fgh.def || echo >&2 FAIL
1018		exec 3>&1
1019		echo X $( f >&- & sleep 1; g >&- & sleep 1 ; h ) Y
1020		sleep 3
1021		exec 4>&1 || echo FD_FAIL
1022	' | atf_check -s exit:0 -o inline:'fghX Y\nGF' -e empty ${TEST_SH} ||
1023		atf_fail "48875 stdin variant failure"
1024}
1025
1026atf_init_test_cases() {
1027	atf_add_test_case basic_test_method_test
1028	atf_add_test_case do_input_redirections
1029	atf_add_test_case do_output_redirections
1030	atf_add_test_case do_redirect_input_output
1031	atf_add_test_case fd_redirections
1032	atf_add_test_case local_redirections
1033	atf_add_test_case incorrect_redirections
1034	atf_add_test_case named_fd_redirections
1035	atf_add_test_case redir_here_doc
1036	atf_add_test_case redir_in_case
1037	atf_add_test_case subshell_redirections
1038	atf_add_test_case ulimit_redirection_interaction
1039	atf_add_test_case validate_fn_redirects
1040}
1041