1# $NetBSD: t_builtins.sh,v 1.6 2021/05/18 21:37:56 kre Exp $
2#
3# Copyright (c) 2018 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#
31# This file tests the various sh builtin utilities.
32#
33# Those utilities that are really external programs, which are builtin in
34# for (mostly) performance (printf, kill, test, ...), are tested elsewhere.
35# We do test the builtin "echo" here as (in NetBSD) it is different than
36# the external one.
37#
38# The (mostly special) builtins which appear to be more syntax than command
39# are tested in other test programs, rather than here (break, continue...)
40#
41# And finally, those which are fundamental to the operation of the shell,
42# like wait, set, shift, ... are also tested in other test programs where
43# all their operations can be more thoroughly verified.
44#
45# This leaves those which need to be built in (cd, umask, ...) but whose
46# purpose is mostly to alter the environment in which the shell operates
47# of that of the commands it runs.   These tests act in co-operation with
48# other tests exist here (where thy do) by not duplicating tests run
49# elsewhere (ulimit is one example) but just adding to those.
50# One day these might be unified.
51#
52# We do test both standard use of the builtins (where they are standard)
53# and NetBSD sh extensions (when run on a shell with no support, such tests
54# should be skipped.)
55#
56
57# Utility function able to test whether most of the builtins exist in
58# the shell being tested.
59have_builtin()
60{
61	${TEST_SH} -c "( $3 $1 $4 ) >/dev/null 2>&1" 	&&
62	LC_ALL=C ${TEST_SH} -c \
63	    'case "$( (type '"$1"') 2>&1)" in
64		(*built*)	exit 0 ;;
65		(*reserved*)	exit 0 ;;   # zsh!! (reserved words are builtin)
66	     esac
67	     exit 1'					||
68	{
69		test -z "$2" && atf_skip "${TEST_SH} has no '$1$5' built-in"
70		return 1;
71	}
72
73	return 0
74}
75
76# And another to test if the shell being tested is the NetBSD shell,
77# as we use these tests both to test standards conformance (correctness)
78# which should be possible for all shells, and to test NetBSD
79# extensions (which we mostly do by testing if the extension exists)
80# and NetBSD sh behaviour for what is unspecified by the standard
81# (so we will be notified via test failure should that unspecified
82# behaviour alter) for which we have to discover if that shell is the
83# one being tested.
84
85is_netbsd_sh()
86{
87	unset NETBSD_SHELL 2>/dev/null
88	test -n "$( ${TEST_SH} -c 'printf %s "${NETBSD_SHELL}"')"
89}
90
91### Helper functions
92
93nl='
94'
95reset()
96{
97	TEST_NUM=0
98	TEST_FAILURES=''
99	TEST_FAIL_COUNT=0
100	TEST_ID="$1"
101
102	# These are used in check()
103	atf_require_prog tr
104	atf_require_prog printf
105	atf_require_prog mktemp
106}
107
108# Test run & validate.
109#
110#	$1 is the command to run (via sh -c)
111#	$2 is the expected output
112#	$3 is the expected exit status from sh
113#	$4 is optional extra data for the error msg (if there is one)
114#
115# Stderr is exxpected to be empty, unless the expected exit code ($3) is != 0
116# in which case some message there is expected (and nothing is a failure).
117# When non-zero exit is expected, we note a different (non-zero) value
118# observed, but do not fail the test because of that.
119
120check()
121{
122	fail=false
123	TEMP_FILE=$( mktemp OUT.XXXXXX )
124	TEST_NUM=$(( $TEST_NUM + 1 ))
125	MSG=
126
127	# our local shell (ATF_SHELL) better do quoting correctly...
128	# some of the tests expect us to expand $nl internally...
129	CMD="$1"
130
131	# determine what the test generates, preserving trailing \n's
132	result="$( ${TEST_SH} -c "${CMD}" 2>"${TEMP_FILE}" && printf X )"
133	STATUS=$?
134	result="${result%X}"
135
136
137	if [ "${STATUS}" -ne "$3" ]; then
138		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
139		MSG="${MSG} expected exit code $3, got ${STATUS}"
140
141		# don't actually fail just because of wrong exit code
142		# unless we either expected, or received "good"
143		# or something else is detected as incorrect as well.
144		case "$3/${STATUS}" in
145		(*/0|0/*) fail=true;;
146		esac
147	fi
148
149	if [ "$3" -eq 0 ]; then
150		if [ -s "${TEMP_FILE}" ]; then
151			MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
152			MSG="${MSG} Messages produced on stderr unexpected..."
153			MSG="${MSG}${nl}$( cat "${TEMP_FILE}" )"
154			fail=true
155		fi
156	else
157		if ! [ -s "${TEMP_FILE}" ]; then
158			MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
159			MSG="${MSG} Expected messages on stderr,"
160			MSG="${MSG} nothing produced"
161			fail=true
162		fi
163	fi
164	rm -f "${TEMP_FILE}"
165
166	if [ "$2" != "${result}" ]
167	then
168		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
169		MSG="${MSG} Expected: <<$2>>, received: <<$result>>"
170		fail=true
171	fi
172
173	if $fail
174	then
175		if [ -n "$4" ]; then
176			MSG="${MSG}${MSG:+${nl}}[$TEST_NUM] Note: ${4}"
177		fi
178		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
179 		MSG="${MSG} Full command: <<${CMD}>>"
180	fi
181
182	$fail && test -n "$TEST_ID" && {
183		TEST_FAILURES="${TEST_FAILURES}${TEST_FAILURES:+${nl}}"
184		TEST_FAILURES="${TEST_FAILURES}${TEST_ID}[$TEST_NUM]:"
185		TEST_FAILURES="${TEST_FAILURES} Test of <<$1>> failed.";
186		TEST_FAILURES="${TEST_FAILURES}${nl}${MSG}"
187		TEST_FAIL_COUNT=$(( $TEST_FAIL_COUNT + 1 ))
188		return 0
189	}
190	$fail && atf_fail "Test[$TEST_NUM] failed: $(
191	    # ATF does not like newlines in messages, so change them...
192		    printf '%s' "${MSG}" | tr '\n' ';'
193	    )"
194	return 0
195}
196
197results()
198{
199	test -n "$1" && atf_expect_fail "$1"
200
201	test -z "${TEST_ID}" && return 0
202	test -z "${TEST_FAILURES}" && return 0
203
204	echo >&2 "=========================================="
205	echo >&2 "While testing '${TEST_ID}'"
206	echo >&2 " - - - - - - - - - - - - - - - - -"
207	echo >&2 "${TEST_FAILURES}"
208
209	atf_fail \
210 "Test ${TEST_ID}: $TEST_FAIL_COUNT (of $TEST_NUM) subtests failed - see stderr"
211}
212
213####### End helpers
214
215atf_test_case colon
216colon_head() {
217	atf_set "descr" "Tests the shell special builtin ':' command"
218}
219colon_body() {
220	have_builtin : || return 0
221
222	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c ":"
223
224	# ':' is a special builtin, so we should exit on redirect error
225	# and variable assignments should persist (stupid, but it is the rule)
226
227	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
228		": >/foo/bar; printf %s No-exit-BUG"
229	atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
230		'X=BUG; X=OK : ; printf %s "${X}"'
231}
232
233atf_test_case echo
234echo_head() {
235	atf_set "descr" "Tests the shell builtin version of echo"
236}
237echo_body() {
238	have_builtin echo || return 0
239
240	if ! is_netbsd_sh; then
241		atf_skip \
242	   "${TEST_SH%% *} is not the NetBSD shell, this test is for it alone"
243		return 0
244	fi
245
246	reset echo
247
248	check 'echo "hello world"' "hello world${nl}" 0
249	check 'echo hello world' "hello world${nl}" 0
250	check 'echo -n "hello world"' "hello world" 0
251	check 'IFS=:; echo hello world' "hello world${nl}" 0
252	check 'IFS=; echo hello world' "hello world${nl}" 0
253
254	check 'echo -e "hello world"' "hello world${nl}" 0
255	check 'echo -e hello world' "hello world${nl}" 0
256	check 'IFS=:; echo -e hello world' "hello world${nl}" 0
257
258	# only one of the options is used
259	check 'echo -e -n "hello world"' "-n hello world${nl}" 0
260	check 'echo -n -e "hello world"' "-e hello world" 0
261	# and only when it is alone
262	check 'echo -en "hello world"' "-en hello world${nl}" 0
263	check 'echo -ne "hello world"' "-ne hello world${nl}" 0
264
265	# echo is specifically required to *not* support --
266	check 'echo -- "hello world"' "-- hello world${nl}" 0
267
268	# similarly any other unknown option is simply part of the output
269	for OPT in a b c v E N Q V 0 1 2 @ , \? \[ \] \( \; . \* -help -version
270	do
271		check "echo '-${OPT}' foo" "-${OPT} foo${nl}" 0
272	done
273
274	# Now test the \\ expansions, with and without -e
275
276	# We rely upon printf %b (tested elsewhere, not only a sh test)
277	# to verify the output when the \\ is supposed to be expanded.
278
279	for E in '' -e
280	do
281		for B in a b c e f n r t v \\ 04 010 012 0177
282		do
283			S="test string with \\${B} in it"
284			if [ -z "${E}" ]; then
285				R="${S}${nl}"
286			else
287				R="$(printf '%b\nX' "${S}")"
288				R=${R%X}
289			fi
290			check "echo $E '${S}'" "${R}" 0
291		done
292	done
293
294	check 'echo foo >&-' "" 1
295	check 'echo foo >&- 2>&-; echo $?; echo $?' "1${nl}0${nl}" 0
296
297	results
298}
299
300atf_test_case eval
301eval_head() {
302	atf_set "descr" "Tests the shell special builtin 'eval'"
303}
304eval_body() {
305	have_builtin eval || return 0
306
307	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'eval "exit 0"'
308	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c 'eval "exit 1"'
309	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'eval exit 0'
310	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c 'eval exit 1'
311
312	atf_check -s exit:0 -e empty -o inline:0 ${TEST_SH} -c \
313		'eval true; printf %d $?'
314	atf_check -s exit:0 -e empty -o inline:1 ${TEST_SH} -c \
315		'eval false; printf %d $?'
316
317	atf_check -s exit:0 -e empty -o inline:abc ${TEST_SH} -c \
318		'X=a Y=b Z=c; for V in X Y Z; do eval "printf %s \$$V"; done'
319	atf_check -s exit:0 -e empty -o inline:abc ${TEST_SH} -c \
320		'X=a Y=b Z=c; for V in X Y Z; do eval printf %s \$$V; done'
321	atf_check -s exit:0 -e empty -o inline:XYZ ${TEST_SH} -c \
322		'for V in X Y Z; do eval "${V}=${V}"; done; printf %s "$X$Y$Z"'
323
324	# make sure eval'd strings affect the shell environment
325
326	atf_check -s exit:0 -e empty -o inline:/b/ ${TEST_SH} -c \
327		'X=a; eval "X=b"; printf /%s/ "${X-unset}"'
328	atf_check -s exit:0 -e empty -o inline:/b/ ${TEST_SH} -c \
329		'X=a; Y=X; Z=b; eval "$Y=$Z"; printf /%s/ "${X-unset}"'
330	atf_check -s exit:0 -e empty -o inline:/unset/ ${TEST_SH} -c \
331		'X=a; eval "unset X"; printf /%s/ "${X-unset}"'
332	atf_check -s exit:0 -e empty -o inline:// ${TEST_SH} -c \
333		'unset X; eval "X="; printf /%s/ "${X-unset}"'
334	atf_check -s exit:0 -e empty -o inline:'2 y Z ' ${TEST_SH} -c \
335		'set -- X y Z; eval shift; printf "%s " "$#" "$@"'
336
337	# ensure an error in an eval'd string causes the shell to exit
338	# unless 'eval' is preceded by 'command' (in which case the
339	# string is not eval'd but execution continues)
340
341	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
342		'eval "if done"; printf %s status=$?'
343
344	atf_check -s exit:0 -e not-empty -o 'match:status=[1-9]' \
345	    ${TEST_SH} -c \
346		'command eval "if done"; printf %s status=$?'
347
348	atf_check -s not-exit:0 -e not-empty \
349	    -o 'match:status=[1-9]' -o 'not-match:[XY]' ${TEST_SH} -c \
350		 'command eval "printf X; if done; printf Y"
351		  S=$?; printf %s status=$S; exit $S'
352
353	# whether 'X' is output here is (or should be) unspecified.
354	atf_check -s not-exit:0 -e not-empty \
355	    -o 'match:status=[1-9]' -o 'not-match:Y' ${TEST_SH} -c \
356		 'command eval "printf X
357		 		if done
358				printf Y"
359		  S=$?; printf %s status=$S; exit $S'
360
361	if is_netbsd_sh
362	then
363		# but on NetBSD we expect that X to appear...
364		atf_check -s not-exit:0 -e not-empty  -o 'match:X' \
365		    -o 'match:status=[1-9]' -o 'not-match:Y' ${TEST_SH} -c \
366			 'command eval "printf X
367					if done
368					printf Y"
369			  S=$?; printf %s status=$S; exit $S'
370	fi
371}
372
373atf_test_case exec
374exec_head() {
375	atf_set "descr" "Tests the shell special builtin 'exec'"
376}
377exec_body() {
378	have_builtin exec || return 0
379
380	atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
381		'exec printf OK; printf BROKEN; exit 3'
382	atf_check -s exit:3 -e empty -o inline:OKOK ${TEST_SH} -c \
383		'(exec printf OK); printf OK; exit 3'
384}
385
386atf_test_case export
387export_head() {
388	atf_set "descr" "Tests the shell builtin 'export'"
389}
390export_body() {
391	have_builtin export || return 0
392
393	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export VAR'
394	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export VAR=abc'
395	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export V A R'
396	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c \
397		'export V A=1 R=2'
398
399	atf_require_prog printenv
400
401	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
402		'unset VAR || exit 7; export VAR; printenv VAR'
403	atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
404		'unset VAR || exit 7; export VAR=; printenv VAR'
405	atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
406		'unset VAR || exit 7; VAR=; export VAR; printenv VAR'
407	atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
408		'unset VAR || exit 7; export VAR; VAR=; printenv VAR'
409	atf_check -s exit:0 -e empty -o inline:XYZ\\n ${TEST_SH} -c \
410		'unset VAR || exit 7; export VAR=XYZ; printenv VAR'
411	atf_check -s exit:0 -e empty -o inline:ABC\\n ${TEST_SH} -c \
412		'VAR=ABC; export VAR; printenv VAR'
413	atf_check -s exit:0 -e empty -o inline:ABC\\n ${TEST_SH} -c \
414		'unset VAR || exit 7; export VAR; VAR=ABC; printenv VAR'
415	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
416		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR'
417	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
418		'unset VAR || exit 7; export VAR;
419		 VAR=ABC; printenv VAR; VAR=XYZ; printenv VAR'
420
421	# don't check VAR=value, some shells provide meaningless quoting...
422	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
423		${TEST_SH} -c \
424			'VAR=foobar ; export VAR ; export -p'
425	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
426		${TEST_SH} -c \
427			'export VAR=foobar ; export -p'
428	atf_check -s exit:0 -e empty -o match:VAR\$ ${TEST_SH} -c \
429			'unset VAR ; export VAR ; export -p'
430	atf_check -s exit:0 -e empty -o not-match:VAR ${TEST_SH} -c \
431			'export VAR ; unset VAR ; export -p'
432	atf_check -s exit:0 -e empty -o not-match:VAR -o not-match:foobar \
433		${TEST_SH} -c \
434			'VAR=foobar; export VAR ; unset VAR ; export -p'
435
436	atf_check -s exit:0 -e empty -o match:VAR= -o match:FOUND=foobar \
437		${TEST_SH} -c \
438			'export VAR=foobar; V=$(export -p);
439			 unset VAR; eval "$V"; export -p;
440			 printf %s\\n FOUND=${VAR-unset}'
441	atf_check -s exit:0 -e empty -o match:VAR -o match:FOUND=unset \
442		${TEST_SH} -c \
443			'export VAR; V=$(export -p);
444			 unset VAR; eval "$V"; export -p;
445			 printf %s\\n FOUND=${VAR-unset}'
446
447	atf_check -s exit:1 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
448		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
449		unset VAR; printenv VAR; VAR=PQR; printenv VAR'
450	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\nVAR=unset\\nMNO\\n \
451	    ${TEST_SH} -c \
452		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
453		 unset VAR; printf %s\\n "VAR=${VAR-unset}"; printenv VAR;
454		 VAR=PQR; printenv VAR; VAR=MNO; export VAR; printenv VAR'
455}
456
457atf_test_case export_nbsd
458export_nbsd_head() {
459	atf_set "descr" "Tests NetBSD extensions to the shell builtin 'export'"
460}
461export_nbsd_body() {
462	have_builtin "export" "" "" "-n foo" ' -n' || return 0
463
464	atf_require_prog printenv
465
466	atf_check -s exit:1 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
467		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
468		export -n VAR; printenv VAR; VAR=PQR; printenv VAR'
469
470	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\nVAR=XYZ\\nMNO\\n \
471	    ${TEST_SH} -c \
472		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
473		 export -n VAR; printf %s\\n "VAR=${VAR-unset}"; printenv VAR;
474		 VAR=PQR; printenv VAR; VAR=MNO; export VAR; printenv VAR'
475
476	have_builtin "export" "" "" -x ' -x' || return 0
477
478	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
479		'export VAR=exported; export -x VAR; printenv VAR'
480	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
481		'export VAR=exported; export -x VAR; VAR=local; printenv VAR'
482	atf_check -s exit:0 -e empty -o inline:once\\nx\\n ${TEST_SH} -c \
483		'export VAR=exported
484		 export -x VAR
485		 VAR=once printenv VAR
486		 printenv VAR || printf %s\\n x'
487
488	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
489		'export VAR=exported; export -x VAR; export VAR=FOO'
490
491	have_builtin export '' 'export VAR;' '-q VAR' ' -q'  || return 0
492
493	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
494		'unset VAR; VAR=set; export -q VAR'
495	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
496		'export VAR=set; export -q VAR'
497	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
498		'VAR=set; RW=set; export -q VAR RW'
499	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
500		'VAR=set; export RO=set; export -q VAR RO'
501	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
502		'export VAR=set RO=set; export -q VAR RO'
503
504	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
505		'unset VAR; export -q VAR'
506	# next one is the same as the have_builtin test, so "cannot" fail...
507	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
508		'unset VAR; export VAR; export -q VAR'
509
510	# if we have -q we should also have -p var...
511	# What's more, we are testing NetBSD sh, so we know output format.
512
513	atf_check -s exit:0 -e empty -o match:VAR=foobar \
514		${TEST_SH} -c \
515			'VAR=foobar ; export VAR ; export -p VAR'
516	atf_check -s exit:0 -e empty -o inline:1 \
517		${TEST_SH} -c \
518			'VAR=foobar ; export VAR ;
519			printf %d $(export -p VAR | wc -l)'
520	atf_check -s exit:0 -e empty \
521		-o inline:'export VAR=foobar\nexport OTHER\n' \
522		${TEST_SH} -c \
523			'VAR=foobar; export VAR OTHER; export -p VAR OTHER'
524	atf_check -s exit:0 -e empty \
525		-o inline:'export A=aaa\nexport B\nexport D='"''"'\n' \
526		${TEST_SH} -c \
527			'A=aaa D= C=foo; unset B; export A B D;
528			 export -p A B C D'
529}
530
531atf_test_case getopts
532getopts_head() {
533	atf_set "descr" "Tests the shell builtin 'getopts'"
534}
535getopts_body() {
536	have_builtin getopts "" "f() {" "a x; }; f -a" || return 0
537}
538
539atf_test_case jobs
540jobs_head() {
541	atf_set "descr" "Tests the shell builting 'jobs' command"
542}
543jobs_body() {
544	have_builtin jobs || return 0
545
546	atf_require_prog sleep
547
548	# note that POSIX requires that we reference $! otherwise
549	# the shell is not required to remember the process...
550
551	atf_check -s exit:0 -e empty -o match:sleep -o match:Running \
552		${TEST_SH} -c 'sleep 1 & P=$!; jobs; wait'
553	atf_check -s exit:0 -e empty -o match:sleep -o match:Done \
554		${TEST_SH} -c 'sleep 1 & P=$!; sleep 2; jobs; wait'
555}
556
557atf_test_case read
558read_head() {
559	atf_set "descr" "Tests the shell builtin read command"
560}
561read_body() {
562	have_builtin read "" "echo x|" "var" || return 0
563}
564
565atf_test_case readonly
566readonly_head() {
567	atf_set "descr" "Tests the shell builtin 'readonly'"
568}
569readonly_body() {
570	have_builtin readonly || return 0
571
572	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly VAR'
573	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly VAR=abc'
574	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly V A R'
575	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly V A=1 R=2'
576
577	atf_check -s exit:0 -e empty -o inline:unset ${TEST_SH} -c \
578		'unset VAR; readonly VAR; printf %s ${VAR-unset}'
579	atf_check -s exit:0 -e empty -o inline:set ${TEST_SH} -c \
580		'unset VAR; readonly VAR=set; printf %s ${VAR-unset}'
581	atf_check -s exit:0 -e empty -o inline:set ${TEST_SH} -c \
582		'VAR=initial; readonly VAR=set; printf %s ${VAR-unset}'
583	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
584		'readonly VAR=initial; VAR=new; printf %s "${VAR}"'
585
586	# don't check VAR=value, some shells provide meaningless quoting...
587	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
588		${TEST_SH} -c \
589			'VAR=foobar ; readonly VAR ; readonly -p'
590	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
591		${TEST_SH} -c \
592			'readonly VAR=foobar ; readonly -p'
593	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
594		-o not-match:badvalue ${TEST_SH} -c \
595			'VAR=badvalue; readonly VAR=foobar ; readonly -p'
596	atf_check -s exit:0 -e empty -o match:VAR\$ ${TEST_SH} -c \
597			'unset VAR ; readonly VAR ; readonly -p'
598
599	# checking that readonly -p works (to reset stuff) is a pain...
600	# particularly since not all shells say "readonly..." by default
601	atf_check -s exit:0 -e empty -o match:MYVAR= -o match:FOUND=foobar \
602		${TEST_SH} -c \
603			'V=$(readonly MYVAR=foobar; readonly -p | grep " MYVAR")
604			 unset MYVAR; eval "$V"; readonly -p;
605			 printf %s\\n FOUND=${MYVAR-unset}'
606	atf_check -s exit:0 -e empty -o match:MYVAR\$ -o match:FOUND=unset \
607		${TEST_SH} -c \
608			'V=$(readonly MYVAR; readonly -p | grep " MYVAR")
609			 unset MYVAR; eval "$V"; readonly -p;
610			 printf %s\\n "FOUND=${MYVAR-unset}"'
611	atf_check -s exit:0 -e empty -o match:MYVAR= -o match:FOUND=empty \
612		${TEST_SH} -c \
613			'V=$(readonly MYVAR=; readonly -p | grep " MYVAR")
614			 unset VAR; eval "$V"; readonly -p;
615			 printf %s\\n "FOUND=${MYVAR-unset&}${MYVAR:-empty}"'
616
617	# don't test stderr, some shells inist on generating a message for an
618	# unset of a readonly var (rather than simply having unset make $?=1)
619
620	atf_check -s not-exit:0 -e empty -o empty ${TEST_SH} -c \
621		'unset VAR; readonly VAR=set;
622		 unset VAR 2>/dev/null && printf %s ${VAR:-XX}'
623	atf_check -s not-exit:0 -e ignore -o empty ${TEST_SH} -c \
624		'unset VAR; readonly VAR=set; unset VAR && printf %s ${VAR:-XX}'
625	atf_check -s exit:0 -e ignore -o inline:set ${TEST_SH} -c \
626		'unset VAR; readonly VAR=set; unset VAR; printf %s ${VAR-unset}'
627}
628
629atf_test_case readonly_nbsd
630readonly_nbsd_head() {
631	atf_set "descr" "Tests NetBSD extensions to 'readonly'"
632}
633readonly_nbsd_body() {
634	have_builtin readonly '' 'readonly VAR;' '-q VAR' ' -q'  || return 0
635
636	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
637		'VAR=set; readonly -q VAR'
638	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
639		'readonly VAR=set; readonly -q VAR'
640	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
641		'VAR=set; RW=set; readonly -q VAR RW'
642	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
643		'VAR=set; readonly RO=set; readonly -q VAR RO'
644	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
645		'readonly VAR=set RO=set; readonly -q VAR RO'
646
647	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
648		'unset VAR; readonly -q VAR'
649	# next one is the same as the have_builtin test, so "cannot" fail...
650	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
651		'unset VAR; readonly VAR; readonly -q VAR'
652
653	# if we have -q we should also have -p var...
654	# What's more, we are testing NetBSD sh, so we know output format.
655
656	atf_check -s exit:0 -e empty -o match:VAR=foobar \
657		${TEST_SH} -c \
658			'VAR=foobar ; readonly VAR ; readonly -p VAR'
659	atf_check -s exit:0 -e empty -o inline:1 \
660		${TEST_SH} -c \
661			'VAR=foobar ; readonly VAR ;
662			printf %d $(readonly -p VAR | wc -l)'
663	atf_check -s exit:0 -e empty \
664		-o inline:'readonly VAR=foobar\nreadonly OTHER\n' \
665		${TEST_SH} -c \
666			'VAR=foobar; readonly VAR OTHER; readonly -p VAR OTHER'
667	atf_check -s exit:0 -e empty \
668		-o inline:'readonly A=aaa\nreadonly B\nreadonly D='"''"'\n' \
669		${TEST_SH} -c \
670			'A=aaa D= C=foo; unset B; readonly A B D;
671			 readonly -p A B C D'
672}
673
674atf_test_case cd_pwd
675cd_pwd_head() {
676	atf_set "descr" "Tests the shell builtins 'cd' & 'pwd'"
677}
678cd_pwd_body() {
679	have_builtin cd "" "HOME=/;" || return 0
680	have_builtin pwd || return 0
681}
682
683atf_test_case true_false
684true_false_head() {
685	atf_set "descr" "Tests the 'true' and 'false' shell builtin commands"
686}
687true_false_body() {
688	have_builtin true || return 0
689
690	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c true
691
692	# true is not a special builtin, so errors do not cause exit
693	# but we should still get an error from the broken redirect
694	# and the exit status of true should be false...
695
696	atf_check -s exit:0 -e not-empty -o inline:OK ${TEST_SH} -c \
697		"true >/foo/bar && printf %s NOT-; printf %s OK"
698
699	# and var-assigns should not affect the current sh env
700
701	atf_check -s exit:0 -e empty -o inline:IS-OK ${TEST_SH} -c \
702		'X=OK; X=BROKEN true && printf %s IS-; printf %s "${X}"'
703
704	have_builtin false "" ! || return 0
705
706	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c false
707}
708
709atf_test_case type
710type_head() {
711	atf_set "descr" "Tests the sh builtin 'type' command"
712}
713type_body() {
714	have_builtin type "" "" type || return 0
715}
716
717# This currently has its own t_ulimit - either merge that here,
718# or delete this one and keep that...  ulimit -n is also tested in
719# the t_redir tests, as that affects the shell's use of file descriptors
720atf_test_case ulimit
721ulimit_head() {
722	atf_set "descr" "Tests the sh builtin 'ulimit'"
723}
724ulimit_body() {
725	have_builtin ulimit || return 0
726}
727
728atf_test_case umask
729umask_head() {
730	atf_set "descr" "Tests the sh builtin 'umask'"
731}
732umask_body() {
733	have_builtin umask || return 0
734
735	atf_require_prog touch
736	atf_require_prog stat
737	atf_require_prog rm
738	atf_require_prog chmod
739
740	reset umask
741
742	# 8 octal digits
743	for M in 0 1 2 3 4 5 6 7
744	do
745	    # Test numbers start: 1 25 49 73 97 121 145 169
746
747	    # 8 combinations of each to test (64 inner loops)
748	    # 3 tests in each loop, hence 192 subtests in all
749
750		# Test numbers from loop above, plus (below) and the next 2
751		#+     1        4        7         10	     13
752	    for T in "0${M}" "00${M}" "0${M}0" "0${M}00" "0${M}${M}" \
753		"0${M}${M}0" "0${M}${M}${M}"  "0${M}0${M}"
754		#+    16          19		  22
755	    do
756		# umask turns bits off, calculate which bits will be on...
757
758		D=$(( 0777 & ~ T ))		# for directories
759		F=$(( $D & ~ 0111 ))		# and files with no 'x' bits
760
761		# Note: $(( )) always produces decimal, so we test that format
762		# (see '%d' in printf of stat result)
763
764		# need chmod or we might have no perm to rmdir TD
765		{ chmod +rwx TF TFT TD; rm -fr TF TFT TD; } 2>/dev/null || :
766
767		# check that the umask applies to files created by the shell
768		check \
769		  "umask $T; > TF; printf %d \$(stat -nf %#Lp TF)" \
770				  "$F" 0 "$F is $(printf %#o $F)" # 1 4 7 10 ...
771
772		# and to files created by commands that the shell runs
773		check \
774		  "umask $T; touch TFT; printf %d \$(stat -nf %#Lp TFT)" \
775				  "$F" 0 "$F is $(printf %#o $F)" # 2 5 8 11 ...
776
777		# and to directories created b ... (directories keep 'x')
778		check \
779		  "umask $T; mkdir TD; printf %d \$(stat -nf %#Lp TD)" \
780				  "$D" 0 "$D is $(printf %#o $D)" # 3 6 9 12 ...
781	    done
782	done
783
784	# Now add a few more tests with less regular u/g/m masks
785	# In here, include tests where umask value has no leading '0'
786
787	# 10 loops, the same 3 tests in each loop, 30 more subtests
788	# from 193 .. 222
789
790	#        193 196 199  202 205 208 211  214  217 220
791	for T in 013 047 722 0772 027 123 421 0124 0513 067
792	do
793		D=$(( 0777 & ~ 0$T ))
794		F=$(( $D & ~ 0111 ))
795
796		{ chmod +rwx TF TFT TD; rm -fr TF TFT TD; } 2>/dev/null || :
797
798		check \
799		  "umask $T; > TF; printf %d \$(stat -nf %#Lp TF)" \
800				  "$F" 0 "$F is $(printf %#o $F)"	# +0
801
802		check \
803		  "umask $T; touch TFT; printf %d \$(stat -nf %#Lp TFT)" \
804				  "$F" 0 "$F is $(printf %#o $F)"	# +1
805
806		check \
807		  "umask $T; mkdir TD; printf %d \$(stat -nf %#Lp TD)" \
808				  "$D" 0 "$D is $(printf %#o $D)"	# +2
809	done
810
811	results
812}
813
814atf_test_case unset
815unset_head() {
816	atf_set "descr" "Tests the sh builtin 'unset'"
817}
818unset_body() {
819	have_builtin unset || return 0
820}
821
822atf_test_case hash
823hash_head() {
824	atf_set "descr" "Tests the sh builtin 'hash' (ash extension)"
825}
826hash_body() {
827	have_builtin hash || return 0
828}
829
830atf_test_case jobid
831jobid_head() {
832	atf_set "descr" "Tests sh builtin 'jobid' (NetBSD extension)"
833}
834jobid_body() {
835
836	# have_builtin jobid || return 0	No simple jobid command test
837	$TEST_SH -c '(exit 0)& jobid $!' >/dev/null 2>&1  || {
838		atf_skip "${TEST_SH} has no 'jobid' built-in"
839		return 0
840	}
841}
842
843atf_test_case let
844let_head() {
845	atf_set "descr" "Tests the sh builtin 'let' (common extension from ksh)"
846}
847let_body() {
848	have_builtin let "" "" 1 || return 0
849}
850
851atf_test_case local
852local_head() {
853	atf_set "descr" "Tests the shell builtin 'local' (common extension)"
854}
855local_body() {
856	have_builtin local "" "f () {" "X; }; f" || return 0
857}
858
859atf_test_case setvar
860setvar_head() {
861	atf_set "descr" "Tests the shell builtin 'setvar' (BSD extension)"
862}
863setvar_body() {
864	have_builtin setvar || return 0
865
866	atf_check -s exit:0 -e empty -o inline:foo ${TEST_SH} -c \
867		'unset PQ && setvar PQ foo; printf %s "${PQ-not set}"'
868	atf_check -s exit:0 -e empty -o inline:abcd ${TEST_SH} -c \
869		'for x in a b c d; do setvar "$x" "$x"; done;
870		 printf %s "$a$b$c$d"'
871	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c \
872		'a=1; b=2; c=3; d=4
873		 for x in a b c d; do setvar "$x" ""; done;
874		 printf %s "$a$b$c$d"'
875}
876
877atf_test_case fdflags
878fdflags_head() {
879	atf_set "descr" \
880	   "Tests basic operation of sh builtin 'fdflags' (NetBSD extension)"
881}
882fdflags_body() {
883	have_builtin fdflags || return 0
884}
885
886atf_test_case fdflags__s
887fdflags__s_head() {
888	atf_set "descr" "Checks setting/clearing flags on file descriptors"
889}
890fdflags__s_body() {
891	have_builtin fdflags || return 0
892}
893
894atf_test_case fdflags__v
895fdflags__v_head() {
896	atf_set "descr" "Checks verbose operation of fdflags"
897}
898fdflags__v_body() {
899	have_builtin fdflags || return 0
900}
901
902atf_test_case fdflags__v_s
903fdflags__v_s_head() {
904	atf_set "descr" "tests verbose operation of fdflags -s"
905}
906fdflags__v_s_body() {
907	have_builtin fdflags || return 0
908}
909
910atf_test_case fdflags_multiple_fd
911fdflags_multiple_fd_head() {
912	atf_set "descr" "Checks operation of fdflags with more than one fd"
913}
914fdflags_multiple_fd_body() {
915	have_builtin fdflags || return 0
916}
917
918atf_test_case fdflags_one_flag_at_a_time
919fdflags_one_flag_at_a_time_head() {
920	atf_set "descr" "Tests all possible fdflags flags, and combinations"
921}
922fdflags_one_flag_at_a_time_body() {
923	have_builtin fdflags || return 0
924}
925
926atf_test_case fdflags_save_restore
927fdflags_save_restore_head() {
928	atf_set "descr" 'Verify that fd flags can be saved and restored'
929}
930fdflags_save_restore_body() {
931	have_builtin fdflags || return 0
932}
933
934atf_test_case fdflags_names_abbreviated
935fdflags_names_abbreviated_head() {
936	atf_set "descr" 'Tests using abbreviated names for fdflags'
937}
938fdflags_names_abbreviated_body() {
939	have_builtin fdflags || return 0
940}
941
942atf_test_case fdflags_xx_errors
943fdflags_xx_errors_head() {
944	atf_set "descr" 'Check various erroneous fdflags uses'
945}
946fdflags_xx_errors_body() {
947	have_builtin fdflags || return 0
948}
949
950
951atf_init_test_cases() {
952
953	# "standard" builtin commands in sh
954
955	# no tests of the "very special" (almost syntax) builtins
956	#  (break/continue/return) - they're tested enough elsewhere
957
958	atf_add_test_case cd_pwd
959	atf_add_test_case colon
960	atf_add_test_case echo
961	atf_add_test_case eval
962	atf_add_test_case exec
963	atf_add_test_case export
964	atf_add_test_case getopts
965	atf_add_test_case jobs
966	atf_add_test_case read
967	atf_add_test_case readonly
968	atf_add_test_case true_false
969	atf_add_test_case type
970	atf_add_test_case ulimit
971	atf_add_test_case umask
972	atf_add_test_case unset
973
974	# exit/wait/set/shift/trap/alias/unalias/. should have their own tests
975	# fc/times/fg/bg/%    are too messy to contemplate for now
976	# command ??  (probably should have some tests)
977
978	# Note that builtin versions of, printf, kill, ... are tested separately
979	# (these are all "optional" builtins)
980	# (echo is tested here because NetBSD sh builtin echo and /bin/echo
981	#  are different)
982
983	atf_add_test_case export_nbsd
984	atf_add_test_case hash
985	atf_add_test_case jobid
986	atf_add_test_case let
987	atf_add_test_case local
988	atf_add_test_case readonly_nbsd
989	atf_add_test_case setvar
990	# inputrc should probably be tested in libedit tests (somehow)
991
992	# fdflags has a bunch of test cases
993
994	# Always run one test, so we get at least "skipped" result
995	atf_add_test_case fdflags
996
997	# but no need to say "skipped" lots more times...
998	have_builtin fdflags available && {
999		atf_add_test_case fdflags__s
1000		atf_add_test_case fdflags__v
1001		atf_add_test_case fdflags__v_s
1002		atf_add_test_case fdflags_multiple_fd
1003		atf_add_test_case fdflags_names_abbreviated
1004		atf_add_test_case fdflags_one_flag_at_a_time
1005		atf_add_test_case fdflags_save_restore
1006		atf_add_test_case fdflags_xx_errors
1007	}
1008	return 0
1009}
1010