1#! test/for/moderni/sh
2# See the file LICENSE in the main modernish directory for the licence.
3
4# Regression tests related to file descriptors, redirection, pipelines, and other I/O matters.
5
6TEST title='blocks can save a closed file descriptor'
7	# zsh-5.0.7 displays an error when trying to close an already-closed file
8	# descriptor, but the exit status is still 0, so catch stderr output.
9	v=$(set +x; exec 2>&1; { :; } 4>&-)
10	str empty $v || return 1
11	# Now check for correct BUG_SCLOSEDFD detection
12	{
13		{
14			while :; do
15				{
16					exec 4>/dev/null
17				} 4>&-
18				break
19			done 4>&-
20			# does the 4>/dev/null leak out of of both a loop and a { ...; } block?
21			if { true >&4; } 2>/dev/null; then
22				mustHave BUG_SCLOSEDFD
23			else
24				mustNotHave BUG_SCLOSEDFD
25			fi
26		} 4>&-
27	} 4>/dev/null	# BUG_SCLOSEDFD workaround
28	if eq $? 1 || { true >&4; } 2>/dev/null; then
29		return 1
30	elif isset xfailmsg; then
31		return 2
32	fi
33ENDT 4>&-
34
35TEST title="pipeline commands are run in subshells"
36	# POSIX says at http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_12
37	#	"[...] as an extension, however, any or all commands in
38	#	a pipeline may be executed in the current environment."
39	# Some shells execute the last element of a pipeline in the current environment (feature ID:
40	# LEPIPEMAIN), but there are no currently existing shells that execute any other element of a
41	# pipeline in the current environment. Scripts may break if a shell ever does. At the very least
42	# it would require another modernish feature ID (e.g. ALLPIPEMAIN). Until then, this sanity check
43	# should fail if that condition is ever detected.
44	v1= v2= v3= v4=
45	# QRK_APIPEMAIN compat: use assignment-arguments, not real assignments
46	# QRK_PPIPEMAIN compat: don't use assignments in parameter substitutions, eg. : ${v1=1}
47	export v1=1 | export v2=2 | export v3=3 | export v4=4
48	v=$v1$v2$v3$v4
49	unset -v v1 v2 v3 v4
50	case $v in
51	( '' )	mustNotHave LEPIPEMAIN ;;
52	( 4 )	mustHave LEPIPEMAIN ;;
53	(1234)	failmsg="need ALLPIPEMAIN feature ID"; return 1 ;;
54	( * )	failmsg="need new shell quirk ID ($v1$v2$v3$v4)"; return 1 ;;
55	esac
56ENDT
57
58TEST title='simple assignments in pipeline elements'
59	unset -v v1 v2
60	# LEPIPEMAIN compat: no assignment in last element
61	true | v1=foo | putln "junk" | v2=bar | cat
62	case ${v1-U},${v2-U} in
63	( U,U )	mustNotHave QRK_APIPEMAIN ;;
64	( foo,bar )
65		mustHave QRK_APIPEMAIN ;;
66	( * )	return 1 ;;
67	esac
68ENDT
69
70TEST title='param substitutions in pipeline elements'
71	unset -v v1 v2
72	# LEPIPEMAIN compat: no param subst in last element
73	true | : ${v1=foo} | putln "junk" | : ${v2=bar} | cat
74	case ${v1-U},${v2-U} in
75	( U,U )	mustNotHave QRK_PPIPEMAIN ;;
76	( foo,bar )
77		mustHave QRK_PPIPEMAIN ;;
78	( * )	return 1 ;;
79	esac
80ENDT
81
82TEST title="'>>' redirection can create new file"
83	{ put '' >>$tempdir/io-test5; } 2>/dev/null && mustNotHave BUG_APPENDC || mustHave BUG_APPENDC
84ENDT
85
86TEST title="I/O redir on func defs honoured in pipes"
87	foo() {
88		putln 'redir-ok' 2>/dev/null >&5
89		putln 'fn-ok'
90	} 5>$tempdir/io-test6
91	# On bash 2.05b and 3.0, the redirection is forgotten only if the function
92	# is piped through a command, so we add '| cat' to fail on this.
93	case $(umask 007; foo | cat) in
94	( fn-ok )
95		is reg $tempdir/io-test6 && read v <$tempdir/io-test6 && str eq $v 'redir-ok' || return 1 ;;
96	( * )	return 1 ;;
97	esac
98ENDT
99
100TEST title='globbing works regardless of IFS'
101	push -o noglob IFS
102	set +o noglob
103	IFS=$ASCIICHARS
104	set -- /d?*
105	IFS=	# BUG_IFSGLOBC compat: eliminate glob chars from IFS before popping
106	pop -o noglob IFS
107	for v do
108		if str eq $v /dev; then
109			mustNotHave BUG_IFSGLOBP
110			return
111		fi
112	done
113	mustHave BUG_IFSGLOBP
114ENDT
115
116TEST title="'<>' redirection defaults to stdin"
117	(umask 077; putln ok >$tempdir/io-test8)
118	read v </dev/null <>$tempdir/io-test8
119	case $v in
120	( ok )	mustNotHave BUG_REDIRIO ;;
121	( '' )	mustHave BUG_REDIRIO ;;
122	( * )	return 1 ;;
123	esac
124ENDT
125
126TEST title='redirs and assignments can be alternated'
127	# use 'eval' to delay parse error on zsh 5.0.x
128	(umask 077; eval 'v=1 >$tempdir/iotest9 v=2 2>&2 v=3 3>/dev/null v=4 putln ok' 2>/dev/null)
129	if ne $? 0; then
130		mustHave BUG_REDIRPOS
131		return
132	fi
133	read v <$tempdir/iotest9
134	str eq $v ok || mustHave BUG_REDIRPOS
135ENDT
136
137TEST title='comsubs work with stdout closed outside'
138	{
139		v=$(putln foo 5>/dev/null; command -v break; putln bar)
140	} >&-
141	case $v in
142	( 'break' )
143		# test that the documented BUG_CSUBSTDO workaround works
144		{
145			v=$(: 1>&1; putln foo 5>/dev/null; command -v break; putln bar)
146		} >&-
147		case $v in
148		( foo${CCn}break${CCn}bar )
149			mustHave BUG_CSUBSTDO ;;
150		( * )	return 1 ;;
151		esac ;;
152	( "foo${CCn}break${CCn}bar" )
153		mustNotHave BUG_CSUBSTDO ;;
154	( * )	return 1 ;;
155	esac
156ENDT
157
158TEST title='comsubs correctly strip final linefeeds'
159	v=${CCn}one$CCn$CCn$CCn`
160		false && putln nothing
161	`$CCn$CCn${CCn}two$CCn$CCn$CCn$(
162		true && putln something
163	)${CCn}three$CCn$CCn$CCn$CCn$CCn$(
164		:
165	)$CCn$CCn$CCn
166	case $v in
167	( ${CCn}one$CCn$CCn$CCn$CCn$CCn${CCn}two$CCn$CCn${CCn}something${CCn}three$CCn$CCn$CCn$CCn$CCn$CCn$CCn$CCn )
168		mustNotHave BUG_CSUBRMLF ;;
169	( ${CCn}one$CCn$CCn${CCn}two$CCn$CCn${CCn}something${CCn}three$CCn$CCn$CCn )
170		mustHave BUG_CSUBRMLF ;;
171	( * )	return 1 ;;
172	esac
173ENDT
174
175TEST title='comsubs strip final linefeeds (here-doc)'
176	v=$(
177		thisshellhas BUG_HDOCMASK && umask 077
178		cat <<-end_of_heredoc
179
180		one
181
182
183		$(false && putln nothing)
184
185
186		two
187
188
189		$(true && putln something)
190		three
191
192
193
194
195		` : `
196
197		x
198		end_of_heredoc
199	)
200	case $v in
201	( ${CCn}one$CCn$CCn$CCn$CCn$CCn${CCn}two$CCn$CCn${CCn}something${CCn}three$CCn$CCn$CCn$CCn$CCn$CCn${CCn}x )
202		mustNotHave BUG_CSUBRMLF ;;
203	( ${CCn}one$CCn$CCn${CCn}two$CCn$CCn${CCn}something${CCn}three$CCn${CCn}x )
204		mustHave BUG_CSUBRMLF ;;
205	( * )	return 1 ;;
206	esac
207ENDT
208
209TEST title="put/putln check I/O with SIGPIPE ignored"
210	v=$(
211		set +x
212		{
213			(
214				command trap "" PIPE
215				v=0
216				while let "(v += 1) < 250"; do
217					putln	pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp \
218						pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp \
219						pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp \
220						pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp || exit
221				done 2>/dev/null
222				putln BUG >&2
223			) | true   # pipe into cmd that does not read input; I/O error should occur when pipe buffer is full
224		} 2>&1
225	)
226	case $v in
227	( BUG )	mustHave BUG_PUTIOERR ;;
228	( '' )	mustNotHave BUG_PUTIOERR ;;
229	( * )	shellquote -f failmsg=$v; return 1 ;;
230	esac
231ENDT
232