xref: /titanic_44/usr/src/lib/libshell/common/tests/subshell.sh (revision 1cfa752f4e24c34133009b0f6c139127a5c461de)
1########################################################################
2#                                                                      #
3#               This software is part of the ast package               #
4#          Copyright (c) 1982-2010 AT&T Intellectual Property          #
5#                      and is licensed under the                       #
6#                  Common Public License, Version 1.0                  #
7#                    by AT&T Intellectual Property                     #
8#                                                                      #
9#                A copy of the License is available at                 #
10#            http://www.opensource.org/licenses/cpl1.0.txt             #
11#         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         #
12#                                                                      #
13#              Information and Software Systems Research               #
14#                            AT&T Research                             #
15#                           Florham Park NJ                            #
16#                                                                      #
17#                  David Korn <dgk@research.att.com>                   #
18#                                                                      #
19########################################################################
20function err_exit
21{
22	print -u$Error_fd -n "\t"
23	print -u$Error_fd -r ${Command}[$1]: "${@:2}"
24	(( Errors+=1 ))
25}
26alias err_exit='err_exit $LINENO'
27
28Command=${0##*/}
29integer Errors=0 Error_fd=2
30
31tmp=$(mktemp -dt) || { err_exit mktemp -dt failed; exit 1; }
32trap "cd /; rm -rf $tmp" EXIT
33
34bincat=$(PATH=$(getconf PATH) whence -p cat)
35
36z=()
37z.foo=( [one]=hello [two]=(x=3 y=4) [three]=hi)
38z.bar[0]=hello
39z.bar[2]=world
40z.bar[1]=(x=4 y=5)
41val='(
42	typeset -a bar=(
43		[0]=hello
44		[2]=world
45		[1]=(
46			x=4
47			y=5
48		)
49	)
50	typeset -A foo=(
51		[one]=hello
52		[three]=hi
53		[two]=(
54			x=3
55			y=4
56		)
57	)
58)'
59[[ $z == "$val" ]] || err_exit 'compound variable with mixed arrays not working'
60z.bar[1]=yesyes
61[[ ${z.bar[1]} == yesyes ]] || err_exit 'reassign of index array compound variable fails'
62z.bar[1]=(x=12 y=5)
63[[ ${z.bar[1]} == $'(\n\tx=12\n\ty=5\n)' ]] || err_exit 'reassign array simple to compound variable fails'
64eval val="$z"
65(
66	z.foo[three]=good
67	[[ ${z.foo[three]} == good ]] || err_exit 'associative array assignment in subshell not working'
68)
69[[ $z == "$val" ]] || err_exit 'compound variable changes after associative array assignment'
70eval val="$z"
71(
72false
73	z.foo[two]=ok
74	[[ ${z.foo[two]} == ok ]] || err_exit 'associative array assignment to compound variable in subshell not working'
75	z.bar[1]=yes
76	[[ ${z.bar[1]} == yes ]] || err_exit 'index array assignment to compound variable in subshell not working'
77)
78[[ $z == "$val" ]] || err_exit 'compound variable changes after associative array assignment'
79
80x=(
81	foo=( qqq=abc rrr=def)
82	bar=( zzz=no rst=fed)
83)
84eval val="$x"
85(
86	unset x.foo
87	[[ ${x.foo.qqq} ]] && err_exit 'x.foo.qqq should be unset'
88	x.foo=good
89	[[ ${x.foo} == good ]] || err_exit 'x.foo should be good'
90)
91[[ $x == "$val" ]] || err_exit 'compound variable changes after unset leaves'
92unset l
93(
94	l=( a=1 b="BE" )
95)
96[[ ${l+foo} != foo ]] || err_exit 'l should be unset'
97
98Error_fd=9
99eval "exec $Error_fd>&2 2>/dev/null"
100
101TEST_notfound=notfound
102while	whence $TEST_notfound >/dev/null 2>&1
103do	TEST_notfound=notfound-$RANDOM
104done
105
106integer BS=1024 nb=64 ss=60 bs no
107for bs in $BS 1
108do	$SHELL -c '
109		{
110			sleep '$ss'
111			kill -KILL $$
112		} &
113		set -- $(printf %.'$(($BS*$nb))'c x | dd bs='$bs')
114		print ${#1}
115		kill $!
116	' > $tmp/sub 2>/dev/null
117	no=$(<$tmp/sub)
118	(( no == (BS * nb) )) || err_exit "shell hangs on command substitution output size >= $BS*$nb with write size $bs -- expected $((BS*nb)), got ${no:-0}"
119done
120# this time with redirection on the trailing command
121for bs in $BS 1
122do	$SHELL -c '
123		{
124			sleep 2
125			sleep '$ss'
126			kill -KILL $$
127		} &
128		set -- $(printf %.'$(($BS*$nb))'c x | dd bs='$bs' 2>/dev/null)
129		print ${#1}
130		kill $!
131	' > $tmp/sub 2>/dev/null
132	no=$(<$tmp/sub)
133	(( no == (BS * nb) )) || err_exit "shell hangs on command substitution output size >= $BS*$nb with write size $bs and trailing redirection -- expected $((BS*nb)), got ${no:-0}"
134done
135
136# exercise command substitutuion trailing newline logic w.r.t. pipe vs. tmp file io
137
138set -- \
139	'post-line print'								\
140	'$TEST_unset; ($TEST_fork; print 1); print'					\
141	1										\
142	'pre-line print'								\
143	'$TEST_unset; ($TEST_fork; print); print 1'					\
144	$'\n1'										\
145	'multiple pre-line print'							\
146	'$TEST_unset; ($TEST_fork; print); print; ($TEST_fork; print 1); print'		\
147	$'\n\n1'									\
148	'multiple post-line print'							\
149	'$TEST_unset; ($TEST_fork; print 1); print; ($TEST_fork; print); print'		\
150	1										\
151	'intermediate print'								\
152	'$TEST_unset; ($TEST_fork; print 1); print; ($TEST_fork; print 2); print'	\
153	$'1\n\n2'									\
154	'simple variable'								\
155	'$TEST_unset; ($TEST_fork; l=2; print "$l"); print $l'				\
156	2										\
157	'compound variable'								\
158	'$TEST_unset; ($TEST_fork; l=(a=2 b="BE"); print "$l"); print $l'		\
159	$'(\n\ta=2\n\tb=BE\n)'								\
160
161export TEST_fork TEST_unset
162
163while	(( $# >= 3 ))
164do	txt=$1
165	cmd=$2
166	exp=$3
167	shift 3
168	for TEST_unset in '' 'unset var'
169	do	for TEST_fork in '' 'ulimit -c 0'
170		do	for TEST_shell in "eval" "$SHELL -c"
171			do	if	! got=$($TEST_shell "$cmd")
172				then	err_exit "${TEST_shell/*-c/\$SHELL -c} ${TEST_unset:+unset }${TEST_fork:+fork }$txt print failed"
173				elif	[[ "$got" != "$exp" ]]
174				then	EXP=$(printf %q "$exp")
175					GOT=$(printf %q "$got")
176					err_exit "${TEST_shell/*-c/\$SHELL -c} ${TEST_unset:+unset }${TEST_fork:+fork }$txt command substitution failed -- expected $EXP, got $GOT"
177				fi
178			done
179		done
180	done
181done
182
183r=$( ($SHELL -c '
184	{
185		sleep 32
186		kill -KILL $$
187	} &
188	for v in $(set | sed "s/=.*//")
189	do	command unset $v
190	done
191	typeset -Z5 I
192	for ((I = 0; I < 1024; I++))
193	do	eval A$I=1234567890
194	done
195	a=$(set 2>&1)
196	print ok
197	kill -KILL $!
198') 2>/dev/null)
199[[ $r == ok ]] || err_exit "large subshell command substitution hangs"
200
201for TEST_command in '' $TEST_notfound
202do	for TEST_exec in '' 'exec'
203	do	for TEST_fork in '' 'ulimit -c 0;'
204		do	for TEST_redirect in '' '>/dev/null'
205			do	for TEST_substitute in '' ': $'
206				do
207
208	TEST_test="$TEST_substitute($TEST_fork $TEST_exec $TEST_command $TEST_redirect)"
209	[[ $TEST_test == '('*([[:space:]])')' ]] && continue
210	r=$($SHELL -c '
211		{
212			sleep 2
213			kill -KILL $$
214		} &
215		'"$TEST_test"'
216		kill $!
217		print ok
218		')
219	[[ $r == ok ]] || err_exit "shell hangs on $TEST_test"
220
221				done
222			done
223		done
224	done
225done
226
227$SHELL -c '( autoload xxxxx);print -n' ||  err_exit 'autoloaded functions in subshells can cause failure'
228foo=$($SHELL  <<- ++EOF++
229	(trap 'print bar' EXIT;print -n foo)
230	++EOF++
231)
232[[ $foo == foobar ]] || err_exit 'trap on exit when last commands is subshell is not triggered'
233
234err=$(
235	$SHELL  2>&1  <<- \EOF
236	        date=$(whence -p date)
237	        function foo
238	        {
239	                x=$( $date > /dev/null 2>&1 ;:)
240	        }
241		# consume almost all fds to push the test to the fd limit #
242		integer max=$(ulimit --nofile)
243		(( max -= 6 ))
244		for ((i=20; i < max; i++))
245		do	exec {i}>&1
246		done
247	        for ((i=0; i < 20; i++))
248	        do      y=$(foo)
249	        done
250	EOF
251) || {
252	err=${err%%$'\n'*}
253	err=${err#*:}
254	err=${err##[[:space:]]}
255	err_exit "nested command substitution with redirections failed -- $err"
256}
257
258exp=0
259$SHELL -c $'
260	function foobar
261	{
262		print "hello world"
263	}
264	[[ $(getopts \'[+?X\ffoobar\fX]\' v --man 2>&1) == *"Xhello worldX"* ]]
265	exit '$exp$'
266'
267got=$?
268[[ $got == $exp ]] || err_exit "getopts --man runtime callout with nonzero exit terminates shell -- expected '$exp', got '$got'"
269exp=ok
270got=$($SHELL -c $'
271	function foobar
272	{
273		print "hello world"
274	}
275	[[ $(getopts \'[+?X\ffoobar\fX]\' v --man 2>&1) == *"Xhello worldX"* ]]
276	print '$exp$'
277')
278[[ $got == $exp ]] || err_exit "getopts --man runtime callout with nonzero exit terminates shell -- expected '$exp', got '$got'"
279
280# command substitution variations #
281set -- \
282	'$('			')'		\
283	'${ '			'; }'		\
284	'$(ulimit -c 0; '	')'		\
285	'$( ('			') )'		\
286	'${ ('			'); }'		\
287	'`'			'`'		\
288	'`('			')`'		\
289	'`ulimit -c 0; '	'`'		\
290	# end of table #
291exp=ok
292testcase[1]='
293	if	%sexpr "NOMATCH" : ".*Z" >/dev/null%s
294	then	print error
295	else	print ok
296	fi
297	exit %s
298'
299testcase[2]='
300	function bar
301	{
302		pipeout=%1$sprintf Ok | tr O o%2$s
303		print $pipeout
304		return 0
305	}
306	foo=%1$sbar%2$s || foo="exit status $?"
307	print $foo
308	exit %3$s
309'
310while	(( $# >= 2 ))
311do	for ((TEST=1; TEST<=${#testcase[@]}; TEST++))
312	do	body=${testcase[TEST]}
313		for code in 0 2
314		do	got=${ printf "$body" "$1" "$2" "$code" | $SHELL 2>&1 }
315			status=$?
316			if	(( status != code ))
317			then	err_exit "test $TEST '$1...$2 exit $code' failed -- exit status $status, expected $code"
318			elif	[[ $got != $exp ]]
319			then	err_exit "test $TEST '$1...$2 exit $code' failed -- got '$got', expected '$exp'"
320			fi
321		done
322	done
323	shift 2
324done
325
326# the next tests loop on all combinations of
327#	{ SUB CAT INS TST APP } X { file-sizes }
328# where the file size starts at 1Ki and doubles up to and including 1Mi
329#
330# the tests and timeouts are done in async subshells to prevent
331# the test harness from hanging
332
333SUB=(
334	( BEG='$( '	END=' )'	)
335	( BEG='${ '	END='; }'	)
336)
337CAT=(  cat  $bincat  )
338INS=(  ""  "builtin cat; "  "builtin -d cat $bincat; "  ": > /dev/null; "  )
339APP=(  ""  "; :"  )
340TST=(
341	( CMD='print foo | $cat'			EXP=3		)
342	( CMD='$cat < $tmp/lin'						)
343	( CMD='cat $tmp/lin | $cat'					)
344	( CMD='read v < $tmp/buf; print $v'		LIM=4*1024	)
345	( CMD='cat $tmp/buf | read v; print $v'		LIM=4*1024	)
346)
347
348command exec 3<> /dev/null
349if	cat /dev/fd/3 >/dev/null 2>&1
350then	T=${#TST[@]}
351	TST[T].CMD='$cat <(print foo)'
352	TST[T].EXP=3
353fi
354
355# prime the two data files to 512 bytes each
356# $tmp/lin has newlines every 16 bytes and $tmp/buf has no newlines
357# the outer loop doubles the file size at top
358
359buf=$'1234567890abcdef'
360lin=$'\n1234567890abcde'
361for ((i=0; i<5; i++))
362do	buf=$buf$buf
363	lin=$lin$lin
364done
365print -n "$buf" > $tmp/buf
366print -n "$lin" > $tmp/lin
367
368unset SKIP
369for ((n=1024; n<=1024*1024; n*=2))
370do	cat $tmp/buf $tmp/buf > $tmp/tmp
371	mv $tmp/tmp $tmp/buf
372	cat $tmp/lin $tmp/lin > $tmp/tmp
373	mv $tmp/tmp $tmp/lin
374	for ((S=0; S<${#SUB[@]}; S++))
375	do	for ((C=0; C<${#CAT[@]}; C++))
376		do	cat=${CAT[C]}
377			for ((I=0; I<${#INS[@]}; I++))
378			do	for ((A=0; A<${#APP[@]}; A++))
379				do	for ((T=0; T<${#TST[@]}; T++))
380					do	#undent...#
381
382	if	[[ ! ${SKIP[S][C][I][A][T]} ]]
383	then	eval "{ x=${SUB[S].BEG}${INS[I]}${TST[T].CMD}${APP[A]}${SUB[S].END}; print \${#x}; } >\$tmp/out &"
384		m=$!
385		{ sleep 4; kill -9 $m; } &
386		k=$!
387		wait $m
388		h=$?
389		kill -9 $k
390		wait $k
391		got=$(<$tmp/out)
392		if	[[ ! $got ]] && (( h ))
393		then	got=HUNG
394		fi
395		if	[[ ${TST[T].EXP} ]]
396		then	exp=${TST[T].EXP}
397		else	exp=$n
398		fi
399		if	[[ $got != $exp ]]
400		then	# on failure skip similar tests on larger files sizes #
401			SKIP[S][C][I][A][T]=1
402			siz=$(printf $'%#i' $exp)
403			cmd=${TST[T].CMD//\$cat/$cat}
404			cmd=${cmd//\$tmp\/buf/$siz.buf}
405			cmd=${cmd//\$tmp\/lin/$siz.lin}
406			err_exit "'x=${SUB[S].BEG}${INS[I]}${cmd}${APP[A]}${SUB[S].END} && print \${#x}' failed -- expected '$exp', got '$got'"
407		elif	[[ ${TST[T].EXP} ]] || (( TST[T].LIM >= n ))
408		then	SKIP[S][C][I][A][T]=1
409		fi
410	fi
411
412						#...indent#
413					done
414				done
415			done
416		done
417	done
418done
419
420# specifics -- there's more?
421
422{
423	cmd='{ exec 5>/dev/null; print "$(eval ls -d . 2>&1 1>&5)"; } >$tmp/out &'
424	eval $cmd
425	m=$!
426	{ sleep 4; kill -9 $m; } &
427	k=$!
428	wait $m
429	h=$?
430	kill -9 $k
431	wait $k
432	got=$(<$tmp/out)
433} 2>/dev/null
434exp=''
435if	[[ ! $got ]] && (( h ))
436then	got=HUNG
437fi
438if	[[ $got != $exp ]]
439then	err_exit "eval '$cmd' failed -- expected '$exp', got '$got'"
440fi
441
442float t1=$SECONDS
443sleep=$(whence -p sleep)
444if	[[ $sleep ]]
445then
446	$SHELL -c "( $sleep 5 </dev/null >/dev/null 2>&1 & );exit 0" | cat
447	(( (SECONDS-t1) > 4 )) && err_exit '/bin/sleep& in subshell hanging'
448	((t1=SECONDS))
449fi
450$SHELL -c '( sleep 5 </dev/null >/dev/null 2>&1 & );exit 0' | cat
451(( (SECONDS-t1) > 4 )) && err_exit 'sleep& in subshell hanging'
452
453exp=HOME=$HOME
454( HOME=/bin/sh )
455got=$(env | grep ^HOME=)
456[[ $got == "$exp" ]] ||  err_exit "( HOME=/bin/sh ) cleanup failed -- expected '$exp', got '$got'"
457
458cmd='echo $((case x in x)echo ok;esac);:)'
459exp=ok
460got=$($SHELL -c "$cmd" 2>&1)
461[[ $got == "$exp" ]] ||  err_exit "'$cmd' failed -- expected '$exp', got '$got'"
462
463cmd='eval "for i in 1 2; do eval /bin/echo x; done"'
464exp=$'x\nx'
465got=$($SHELL -c "$cmd")
466if	[[ $got != "$exp" ]]
467then	EXP=$(printf %q "$exp")
468	GOT=$(printf %q "$got")
469	err_exit "'$cmd' failed -- expected $EXP, got $GOT"
470fi
471
472exit $Errors
473