#! /usr/bin/atf-sh
# $NetBSD: t_builtins.sh,v 1.9 2026/07/01 10:58:38 kre Exp $
#
# Copyright (c) 2018 The NetBSD Foundation, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# the implementation of "sh" to test
: ${TEST_SH:="/bin/sh"}

#
# This file tests the various sh builtin utilities.
#
# Those utilities that are really external programs, which are builtin in
# for (mostly) performance (printf, kill, test, ...), are tested elsewhere.
# We do test the builtin "echo" here as (in NetBSD) it is different than
# the external one.
#
# The (mostly special) builtins which appear to be more syntax than command
# are tested in other test programs, rather than here (break, continue...)
#
# And finally, those which are fundamental to the operation of the shell,
# like wait, set, shift, ... are also tested in other test programs where
# all their operations can be more thoroughly verified.
#
# This leaves those which need to be built in (cd, umask, ...) but whose
# purpose is mostly to alter the environment in which the shell operates
# of that of the commands it runs.   These tests act in co-operation with
# other tests exist here (where thy do) by not duplicating tests run
# elsewhere (ulimit is one example) but just adding to those.
# One day these might be unified.
#
# We do test both standard use of the builtins (where they are standard)
# and NetBSD sh extensions (when run on a shell with no support, such tests
# should be skipped.)
#

# Utility function able to test whether most of the builtins exist in
# the shell being tested.
have_builtin()
{
	${TEST_SH} -c "( $3 $1 $4 ) >/dev/null 2>&1" 	&&
	LC_ALL=C ${TEST_SH} -c \
	    'case "$( (type '"$1"') 2>&1)" in
		(*built*)	exit 0 ;;
		(*reserved*)	exit 0 ;;   # zsh!! (reserved words are builtin)
	     esac
	     exit 1'					||
	{
		test -z "$2" && atf_skip "${TEST_SH} has no '$1$5' built-in"
		return 1;
	}

	return 0
}

# And another to test if the shell being tested is the NetBSD shell,
# as we use these tests both to test standards conformance (correctness)
# which should be possible for all shells, and to test NetBSD
# extensions (which we mostly do by testing if the extension exists)
# and NetBSD sh behaviour for what is unspecified by the standard
# (so we will be notified via test failure should that unspecified
# behaviour alter) for which we have to discover if that shell is the
# one being tested.

is_netbsd_sh()
{
	unset NETBSD_SHELL 2>/dev/null
	test -n "$( ${TEST_SH} -c 'printf %s "${NETBSD_SHELL}"')"
}

### Helper functions

nl='
'
reset()
{
	TEST_NUM=0
	TEST_FAILURES=''
	TEST_FAIL_COUNT=0
	TEST_ID="$1"

	# These are used in check()
	atf_require_prog tr
	atf_require_prog printf
	atf_require_prog mktemp
}

# Test run & validate.
#
#	$1 is the command to run (via sh -c)
#	$2 is the expected output
#	$3 is the expected exit status from sh
#	$4 is optional extra data for the error msg (if there is one)
#
# Stderr is exxpected to be empty, unless the expected exit code ($3) is != 0
# in which case some message there is expected (and nothing is a failure).
# When non-zero exit is expected, we note a different (non-zero) value
# observed, but do not fail the test because of that.

check()
{
	fail=false
	TEMP_FILE=$( mktemp OUT.XXXXXX )
	TEST_NUM=$(( $TEST_NUM + 1 ))
	MSG=

	# our local shell (ATF_SHELL) better do quoting correctly...
	# some of the tests expect us to expand $nl internally...
	CMD="$1"

	# determine what the test generates, preserving trailing \n's
	result="$( ${TEST_SH} -c "${CMD}" 2>"${TEMP_FILE}" && printf X )"
	STATUS=$?
	result="${result%X}"


	if [ "${STATUS}" -ne "$3" ]; then
		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
		MSG="${MSG} expected exit code $3, got ${STATUS}"

		# don't actually fail just because of wrong exit code
		# unless we either expected, or received "good"
		# or something else is detected as incorrect as well.
		case "$3/${STATUS}" in
		(*/0|0/*) fail=true;;
		esac
	fi

	if [ "$3" -eq 0 ]; then
		if [ -s "${TEMP_FILE}" ]; then
			MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
			MSG="${MSG} Messages produced on stderr unexpected..."
			MSG="${MSG}${nl}$( cat "${TEMP_FILE}" )"
			fail=true
		fi
	else
		if ! [ -s "${TEMP_FILE}" ]; then
			MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
			MSG="${MSG} Expected messages on stderr,"
			MSG="${MSG} nothing produced"
			fail=true
		fi
	fi
	rm -f "${TEMP_FILE}"

	if [ "$2" != "${result}" ]
	then
		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
		MSG="${MSG} Expected: <<$2>>, received: <<$result>>"
		fail=true
	fi

	if $fail
	then
		if [ -n "$4" ]; then
			MSG="${MSG}${MSG:+${nl}}[$TEST_NUM] Note: ${4}"
		fi
		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
 		MSG="${MSG} Full command: <<${CMD}>>"
	fi

	$fail && test -n "$TEST_ID" && {
		TEST_FAILURES="${TEST_FAILURES}${TEST_FAILURES:+${nl}}"
		TEST_FAILURES="${TEST_FAILURES}${TEST_ID}[$TEST_NUM]:"
		TEST_FAILURES="${TEST_FAILURES} Test of <<$1>> failed.";
		TEST_FAILURES="${TEST_FAILURES}${nl}${MSG}"
		TEST_FAIL_COUNT=$(( $TEST_FAIL_COUNT + 1 ))
		return 0
	}
	$fail && atf_fail "Test[$TEST_NUM] failed: $(
	    # ATF does not like newlines in messages, so change them...
		    printf '%s' "${MSG}" | tr '\n' ';'
	    )"
	return 0
}

results()
{
	test -n "$1" && atf_expect_fail "$1"

	test -z "${TEST_ID}" && return 0
	test -z "${TEST_FAILURES}" && return 0

	echo >&2 "=========================================="
	echo >&2 "While testing '${TEST_ID}'"
	echo >&2 " - - - - - - - - - - - - - - - - -"
	echo >&2 "${TEST_FAILURES}"

	atf_fail \
 "Test ${TEST_ID}: $TEST_FAIL_COUNT (of $TEST_NUM) subtests failed - see stderr"
}

####### End helpers

atf_test_case colon
colon_head() {
	atf_set descr "Tests the shell special builtin ':' command"
}
colon_body() {
	have_builtin : || return 0

	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c ":"

	# ':' is a special builtin, so we should exit on redirect error
	# and variable assignments should persist (stupid, but it is the rule)

	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
		": >/foo/bar; printf %s No-exit-BUG"
	atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
		'X=BUG; X=OK : ; printf %s "${X}"'
}

atf_test_case echo
echo_head() {
	atf_set descr "Tests the shell builtin version of echo"
}
echo_body() {
	have_builtin echo || return 0

	if ! is_netbsd_sh; then
		atf_skip \
	   "${TEST_SH%% *} is not the NetBSD shell, this test is for it alone"
		return 0
	fi

	reset echo

	check 'echo "hello world"' "hello world${nl}" 0
	check 'echo hello world' "hello world${nl}" 0
	check 'echo -n "hello world"' "hello world" 0
	check 'IFS=:; echo hello world' "hello world${nl}" 0
	check 'IFS=; echo hello world' "hello world${nl}" 0

	check 'echo -e "hello world"' "hello world${nl}" 0
	check 'echo -e hello world' "hello world${nl}" 0
	check 'IFS=:; echo -e hello world' "hello world${nl}" 0

	# only one of the options is used
	check 'echo -e -n "hello world"' "-n hello world${nl}" 0
	check 'echo -n -e "hello world"' "-e hello world" 0
	# and only when it is alone
	check 'echo -en "hello world"' "-en hello world${nl}" 0
	check 'echo -ne "hello world"' "-ne hello world${nl}" 0

	# echo is specifically required to *not* support --
	check 'echo -- "hello world"' "-- hello world${nl}" 0

	# similarly any other unknown option is simply part of the output
	for OPT in a b c v E N Q V 0 1 2 @ , \? \[ \] \( \; . \* -help -version
	do
		check "echo '-${OPT}' foo" "-${OPT} foo${nl}" 0
	done

	# Now test the \\ expansions, with and without -e

	# We rely upon printf %b (tested elsewhere, not only a sh test)
	# to verify the output when the \\ is supposed to be expanded.

	for E in '' -e
	do
		for B in a b c e f n r t v \\ 04 010 012 0177
		do
			S="test string with \\${B} in it"
			if [ -z "${E}" ]; then
				R="${S}${nl}"
			else
				R="$(printf '%b\nX' "${S}")"
				R=${R%X}
			fi
			check "echo $E '${S}'" "${R}" 0
		done
	done

	check 'echo foo >&-' "" 1
	check 'echo foo >&- 2>&-; echo $?; echo $?' "1${nl}0${nl}" 0

	results
}

atf_test_case eval
eval_head() {
	atf_set descr "Tests the shell special builtin 'eval'"
}
eval_body() {
	have_builtin eval || return 0

	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'eval "exit 0"'
	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c 'eval "exit 1"'
	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'eval exit 0'
	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c 'eval exit 1'

	atf_check -s exit:0 -e empty -o inline:0 ${TEST_SH} -c \
		'eval true; printf %d $?'
	atf_check -s exit:0 -e empty -o inline:1 ${TEST_SH} -c \
		'eval false; printf %d $?'

	atf_check -s exit:0 -e empty -o inline:abc ${TEST_SH} -c \
		'X=a Y=b Z=c; for V in X Y Z; do eval "printf %s \$$V"; done'
	atf_check -s exit:0 -e empty -o inline:abc ${TEST_SH} -c \
		'X=a Y=b Z=c; for V in X Y Z; do eval printf %s \$$V; done'
	atf_check -s exit:0 -e empty -o inline:XYZ ${TEST_SH} -c \
		'for V in X Y Z; do eval "${V}=${V}"; done; printf %s "$X$Y$Z"'

	# make sure eval'd strings affect the shell environment

	atf_check -s exit:0 -e empty -o inline:/b/ ${TEST_SH} -c \
		'X=a; eval "X=b"; printf /%s/ "${X-unset}"'
	atf_check -s exit:0 -e empty -o inline:/b/ ${TEST_SH} -c \
		'X=a; Y=X; Z=b; eval "$Y=$Z"; printf /%s/ "${X-unset}"'
	atf_check -s exit:0 -e empty -o inline:/unset/ ${TEST_SH} -c \
		'X=a; eval "unset X"; printf /%s/ "${X-unset}"'
	atf_check -s exit:0 -e empty -o inline:// ${TEST_SH} -c \
		'unset X; eval "X="; printf /%s/ "${X-unset}"'
	atf_check -s exit:0 -e empty -o inline:'2 y Z ' ${TEST_SH} -c \
		'set -- X y Z; eval shift; printf "%s " "$#" "$@"'

	# ensure an error in an eval'd string causes the shell to exit
	# unless 'eval' is preceded by 'command' (in which case the
	# string is not eval'd but execution continues)

	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
		'eval "if done"; printf %s status=$?'

	atf_check -s exit:0 -e not-empty -o 'match:status=[1-9]' \
	    ${TEST_SH} -c \
		'command eval "if done"; printf %s status=$?'

	atf_check -s not-exit:0 -e not-empty \
	    -o 'match:status=[1-9]' -o 'not-match:[XY]' ${TEST_SH} -c \
		 'command eval "printf X; if done; printf Y"
		  S=$?; printf %s status=$S; exit $S'

	# whether 'X' is output here is (or should be) unspecified.
	atf_check -s not-exit:0 -e not-empty \
	    -o 'match:status=[1-9]' -o 'not-match:Y' ${TEST_SH} -c \
		 'command eval "printf X
		 		if done
				printf Y"
		  S=$?; printf %s status=$S; exit $S'

	if is_netbsd_sh
	then
		# but on NetBSD we expect that X to appear...
		atf_check -s not-exit:0 -e not-empty  -o 'match:X' \
		    -o 'match:status=[1-9]' -o 'not-match:Y' ${TEST_SH} -c \
			 'command eval "printf X
					if done
					printf Y"
			  S=$?; printf %s status=$S; exit $S'
	fi
}

atf_test_case exec
exec_head() {
	atf_set descr "Tests the shell special builtin 'exec'"
}
exec_body() {
	have_builtin exec || return 0

	atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
		'exec printf OK; printf BROKEN; exit 3'
	atf_check -s exit:3 -e empty -o inline:OKOK ${TEST_SH} -c \
		'(exec printf OK); printf OK; exit 3'
}

atf_test_case export
export_head() {
	atf_set descr "Tests the shell builtin 'export'"
}
export_body() {
	have_builtin export || return 0

	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export VAR'
	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export VAR=abc'
	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export V A R'
	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c \
		'export V A=1 R=2'

	atf_require_prog printenv

	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
		'unset VAR || exit 7; export VAR; printenv VAR'
	atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
		'unset VAR || exit 7; export VAR=; printenv VAR'
	atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
		'unset VAR || exit 7; VAR=; export VAR; printenv VAR'
	atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
		'unset VAR || exit 7; export VAR; VAR=; printenv VAR'
	atf_check -s exit:0 -e empty -o inline:XYZ\\n ${TEST_SH} -c \
		'unset VAR || exit 7; export VAR=XYZ; printenv VAR'
	atf_check -s exit:0 -e empty -o inline:ABC\\n ${TEST_SH} -c \
		'VAR=ABC; export VAR; printenv VAR'
	atf_check -s exit:0 -e empty -o inline:ABC\\n ${TEST_SH} -c \
		'unset VAR || exit 7; export VAR; VAR=ABC; printenv VAR'
	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR'
	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
		'unset VAR || exit 7; export VAR;
		 VAR=ABC; printenv VAR; VAR=XYZ; printenv VAR'

	# don't check VAR=value, some shells provide meaningless quoting...
	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
		${TEST_SH} -c \
			'VAR=foobar ; export VAR ; export -p'
	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
		${TEST_SH} -c \
			'export VAR=foobar ; export -p'
	atf_check -s exit:0 -e empty -o match:VAR\$ ${TEST_SH} -c \
			'unset VAR ; export VAR ; export -p'
	atf_check -s exit:0 -e empty -o not-match:VAR ${TEST_SH} -c \
			'export VAR ; unset VAR ; export -p'
	atf_check -s exit:0 -e empty -o not-match:VAR -o not-match:foobar \
		${TEST_SH} -c \
			'VAR=foobar; export VAR ; unset VAR ; export -p'

	atf_check -s exit:0 -e empty -o match:VAR= -o match:FOUND=foobar \
		${TEST_SH} -c \
			'export VAR=foobar; V=$(export -p);
			 unset VAR; eval "$V"; export -p;
			 printf %s\\n FOUND=${VAR-unset}'
	atf_check -s exit:0 -e empty -o match:VAR -o match:FOUND=unset \
		${TEST_SH} -c \
			'export VAR; V=$(export -p);
			 unset VAR; eval "$V"; export -p;
			 printf %s\\n FOUND=${VAR-unset}'

	atf_check -s exit:1 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
		unset VAR; printenv VAR; VAR=PQR; printenv VAR'
	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\nVAR=unset\\nMNO\\n \
	    ${TEST_SH} -c \
		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
		 unset VAR; printf %s\\n "VAR=${VAR-unset}"; printenv VAR;
		 VAR=PQR; printenv VAR; VAR=MNO; export VAR; printenv VAR'
}

atf_test_case export_nbsd
export_nbsd_head() {
	atf_set descr "Tests NetBSD extensions to the shell builtin 'export'"
}
export_nbsd_body() {
	have_builtin "export" "" "" "-n foo" ' -n' || return 0

	atf_require_prog printenv

	atf_check -s exit:1 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
		export -n VAR; printenv VAR; VAR=PQR; printenv VAR'

	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\nVAR=XYZ\\nMNO\\n \
	    ${TEST_SH} -c \
		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
		 export -n VAR; printf %s\\n "VAR=${VAR-unset}"; printenv VAR;
		 VAR=PQR; printenv VAR; VAR=MNO; export VAR; printenv VAR'

	have_builtin "export" "" "" -x ' -x' || return 0

	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
		'export VAR=exported; export -x VAR; printenv VAR'
	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
		'export VAR=exported; export -x VAR; VAR=local; printenv VAR'
	atf_check -s exit:0 -e empty -o inline:once\\nx\\n ${TEST_SH} -c \
		'export VAR=exported
		 export -x VAR
		 VAR=once printenv VAR
		 printenv VAR || printf %s\\n x'

	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
		'export VAR=exported; export -x VAR; export VAR=FOO'

	have_builtin export '' 'export VAR;' '-q VAR' ' -q'  || return 0

	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
		'unset VAR; VAR=set; export -q VAR'
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
		'export VAR=set; export -q VAR'
	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
		'VAR=set; RW=set; export -q VAR RW'
	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
		'VAR=set; export RO=set; export -q VAR RO'
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
		'export VAR=set RO=set; export -q VAR RO'

	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
		'unset VAR; export -q VAR'
	# next one is the same as the have_builtin test, so "cannot" fail...
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
		'unset VAR; export VAR; export -q VAR'

	# if we have -q we should also have -p var...
	# What's more, we are testing NetBSD sh, so we know output format.

	atf_check -s exit:0 -e empty -o match:VAR=foobar \
		${TEST_SH} -c \
			'VAR=foobar ; export VAR ; export -p VAR'
	atf_check -s exit:0 -e empty -o inline:1 \
		${TEST_SH} -c \
			'VAR=foobar ; export VAR ;
			printf %d $(export -p VAR | wc -l)'
	atf_check -s exit:0 -e empty \
		-o inline:'export VAR=foobar\nexport OTHER\n' \
		${TEST_SH} -c \
			'VAR=foobar; export VAR OTHER; export -p VAR OTHER'
	atf_check -s exit:0 -e empty \
		-o inline:'export A=aaa\nexport B\nexport D='"''"'\n' \
		${TEST_SH} -c \
			'A=aaa D= C=foo; unset B; export A B D;
			 export -p A B C D'
}

atf_test_case getopts
getopts_head() {
	atf_set descr "Tests the shell builtin 'getopts'"
}
getopts_body() {
	have_builtin getopts "" "f() {" "a x; }; f -a" || return 0

	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
		f() { while getopts ab: O; do case $O in
			a) A=A;; b) B=${OPTARG};; *) printf FAIL;; esac
		      done; printf %s\\n "$A,$B"; }
		[ "$(f -a)" = A, ] || exit 10
		[ "$(f -a FOO)" = A, ] || exit 11
		[ "$(f -bC)" = ,C ] || exit 20
		[ "$(f -bC BAR)" = ,C ] || exit 21
		[ "$(f -abC)" = A,C ] || exit 30
		[ "$(f -abC BAZ)" = A,C ] || exit 31
		[ "$(f -a -bC)" = A,C ] || exit 40
		[ "$(f -a -- -bC)" = A, ] || exit 41
		[ "$(f -a -b C)" = A,C ] || exit 50
		[ "$(f -a -b -- C)" = A,-- ] || exit 51
		[ "$(f -b C -a)" = A,C ] || exit 60
		[ "$(f -bC -a)" = A,C ] || exit 70
		[ "$(f -bCa)" = ,Ca ] || exit 80
		exit 0'
}

atf_test_case jobs
jobs_head() {
	atf_set descr "Tests the shell builtin 'jobs' command"
}
jobs_body() {
	have_builtin jobs || return 0

	atf_require_prog sleep

	# note that POSIX requires that we reference $! otherwise
	# the shell is not required to remember the process...

	atf_check -s exit:0 -e empty -o match:sleep -o match:Running \
		${TEST_SH} -c 'sleep 1 & P=$!; jobs; wait'
	atf_check -s exit:0 -e empty -o match:sleep -o match:Done \
		${TEST_SH} -c 'sleep 1 & P=$!; sleep 2; jobs; wait'

	# verify that a child can access jobs, immediately after fork
	atf_check -s exit:0 -e empty -o match:sleep -o match:Running \
		${TEST_SH} -c 'sleep 1 & P=$!; printf %s "$(jobs)"; wait'

	# but a grandchild should not be able to do that
	atf_check -s exit:0 -e empty -o empty \
		${TEST_SH} -c 'sleep 1 & P=$!; (printf %s "$(jobs)"); wait'
}

atf_test_case read
read_head() {
	atf_set descr "Tests the shell builtin read command"
}
read_body() {
	have_builtin read "" "echo x|" "var" || return 0
}

atf_test_case readonly
readonly_head() {
	atf_set descr "Tests the shell builtin 'readonly'"
}
readonly_body() {
	have_builtin readonly || return 0

	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly VAR'
	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly VAR=abc'
	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly V A R'
	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly V A=1 R=2'

	atf_check -s exit:0 -e empty -o inline:unset ${TEST_SH} -c \
		'unset VAR; readonly VAR; printf %s ${VAR-unset}'
	atf_check -s exit:0 -e empty -o inline:set ${TEST_SH} -c \
		'unset VAR; readonly VAR=set; printf %s ${VAR-unset}'
	atf_check -s exit:0 -e empty -o inline:set ${TEST_SH} -c \
		'VAR=initial; readonly VAR=set; printf %s ${VAR-unset}'
	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
		'readonly VAR=initial; VAR=new; printf %s "${VAR}"'

	# don't check VAR=value, some shells provide meaningless quoting...
	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
		${TEST_SH} -c \
			'VAR=foobar ; readonly VAR ; readonly -p'
	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
		${TEST_SH} -c \
			'readonly VAR=foobar ; readonly -p'
	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
		-o not-match:badvalue ${TEST_SH} -c \
			'VAR=badvalue; readonly VAR=foobar ; readonly -p'
	atf_check -s exit:0 -e empty -o match:VAR\$ ${TEST_SH} -c \
			'unset VAR ; readonly VAR ; readonly -p'

	# checking that readonly -p works (to reset stuff) is a pain...
	# particularly since not all shells say "readonly..." by default
	atf_check -s exit:0 -e empty -o match:MYVAR= -o match:FOUND=foobar \
		${TEST_SH} -c \
			'V=$(readonly MYVAR=foobar; readonly -p | grep " MYVAR")
			 unset MYVAR; eval "$V"; readonly -p;
			 printf %s\\n FOUND=${MYVAR-unset}'
	atf_check -s exit:0 -e empty -o match:MYVAR\$ -o match:FOUND=unset \
		${TEST_SH} -c \
			'V=$(readonly MYVAR; readonly -p | grep " MYVAR")
			 unset MYVAR; eval "$V"; readonly -p;
			 printf %s\\n "FOUND=${MYVAR-unset}"'
	atf_check -s exit:0 -e empty -o match:MYVAR= -o match:FOUND=empty \
		${TEST_SH} -c \
			'V=$(readonly MYVAR=; readonly -p | grep " MYVAR")
			 unset VAR; eval "$V"; readonly -p;
			 printf %s\\n "FOUND=${MYVAR-unset&}${MYVAR:-empty}"'

	# don't test stderr, some shells insist on generating a message for an
	# unset of a readonly var (rather than simply having unset make $?=1)

	atf_check -s not-exit:0 -e empty -o empty ${TEST_SH} -c \
		'unset VAR; readonly VAR=set;
		 unset VAR 2>/dev/null && printf %s ${VAR:-XX}'
	atf_check -s not-exit:0 -e ignore -o empty ${TEST_SH} -c \
		'unset VAR; readonly VAR=set; unset VAR && printf %s ${VAR:-XX}'
	atf_check -s exit:0 -e ignore -o inline:set ${TEST_SH} -c \
		'unset VAR; readonly VAR=set; unset VAR; printf %s ${VAR-unset}'
}

atf_test_case readonly_nbsd
readonly_nbsd_head() {
	atf_set descr "Tests NetBSD extensions to 'readonly'"
}
readonly_nbsd_body() {
	have_builtin readonly '' 'readonly VAR;' '-q VAR' ' -q'  || return 0

	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
		'VAR=set; readonly -q VAR'
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
		'readonly VAR=set; readonly -q VAR'
	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
		'VAR=set; RW=set; readonly -q VAR RW'
	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
		'VAR=set; readonly RO=set; readonly -q VAR RO'
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
		'readonly VAR=set RO=set; readonly -q VAR RO'

	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
		'unset VAR; readonly -q VAR'
	# next one is the same as the have_builtin test, so "cannot" fail...
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
		'unset VAR; readonly VAR; readonly -q VAR'

	# if we have -q we should also have -p var...
	# What's more, we are testing NetBSD sh, so we know output format.

	atf_check -s exit:0 -e empty -o match:VAR=foobar \
		${TEST_SH} -c \
			'VAR=foobar ; readonly VAR ; readonly -p VAR'
	atf_check -s exit:0 -e empty -o inline:1 \
		${TEST_SH} -c \
			'VAR=foobar ; readonly VAR ;
			printf %d $(readonly -p VAR | wc -l)'
	atf_check -s exit:0 -e empty \
		-o inline:'readonly VAR=foobar\nreadonly OTHER\n' \
		${TEST_SH} -c \
			'VAR=foobar; readonly VAR OTHER; readonly -p VAR OTHER'
	atf_check -s exit:0 -e empty \
		-o inline:'readonly A=aaa\nreadonly B\nreadonly D='"''"'\n' \
		${TEST_SH} -c \
			'A=aaa D= C=foo; unset B; readonly A B D;
			 readonly -p A B C D'
}

atf_test_case cd_pwd
cd_pwd_head() {
	atf_set descr "Tests the shell builtins 'cd' & 'pwd'"
}
cd_pwd_body() {
	have_builtin cd "" "HOME=/;" || return 0
	have_builtin pwd || return 0
}

atf_test_case true_false
true_false_head() {
	atf_set descr "Tests the 'true' and 'false' shell builtin commands"
}
true_false_body() {
	have_builtin true || return 0

	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c true

	# true is not a special builtin, so errors do not cause exit
	# but we should still get an error from the broken redirect
	# and the exit status of true should be false...

	atf_check -s exit:0 -e not-empty -o inline:OK ${TEST_SH} -c \
		"true >/foo/bar && printf %s NOT-; printf %s OK"

	# and var-assigns should not affect the current sh env

	atf_check -s exit:0 -e empty -o inline:IS-OK ${TEST_SH} -c \
		'X=OK; X=BROKEN true && printf %s IS-; printf %s "${X}"'

	have_builtin false "" ! || return 0

	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c false
}

atf_test_case type
type_head() {
	atf_set descr "Tests the sh builtin 'type' command"
}
type_body() {
	have_builtin type "" "" type || return 0
}

# This currently has its own t_ulimit - either merge that here,
# or delete this one and keep that...  ulimit -n is also tested in
# the t_redir tests, as that affects the shell's use of file descriptors
atf_test_case ulimit
ulimit_head() {
	atf_set descr "Tests the sh builtin 'ulimit'"
}
ulimit_body() {
	have_builtin ulimit || return 0
}

atf_test_case umask
umask_head() {
	atf_set descr "Tests the sh builtin 'umask'"
}
umask_body() {
	have_builtin umask || return 0

	atf_require_prog touch
	atf_require_prog stat
	atf_require_prog rm
	atf_require_prog chmod

	reset umask

	# 8 octal digits
	for M in 0 1 2 3 4 5 6 7
	do
	    # Test numbers start: 1 25 49 73 97 121 145 169

	    # 8 combinations of each to test (64 inner loops)
	    # 3 tests in each loop, hence 192 subtests in all

		# Test numbers from loop above, plus (below) and the next 2
		#+     1        4        7         10	     13
	    for T in "0${M}" "00${M}" "0${M}0" "0${M}00" "0${M}${M}" \
		"0${M}${M}0" "0${M}${M}${M}"  "0${M}0${M}"
		#+    16          19		  22
	    do
		# umask turns bits off, calculate which bits will be on...

		D=$(( 0777 & ~ T ))		# for directories
		F=$(( $D & ~ 0111 ))		# and files with no 'x' bits

		# Note: $(( )) always produces decimal, so we test that format
		# (see '%d' in printf of stat result)

		# need chmod or we might have no perm to rmdir TD
		{ chmod +rwx TF TFT TD; rm -fr TF TFT TD; } 2>/dev/null || :

		# check that the umask applies to files created by the shell
		check \
		  "umask $T; > TF; printf %d \$(stat -nf %#Lp TF)" \
				  "$F" 0 "$F is $(printf %#o $F)" # 1 4 7 10 ...

		# and to files created by commands that the shell runs
		check \
		  "umask $T; touch TFT; printf %d \$(stat -nf %#Lp TFT)" \
				  "$F" 0 "$F is $(printf %#o $F)" # 2 5 8 11 ...

		# and to directories created b ... (directories keep 'x')
		check \
		  "umask $T; mkdir TD; printf %d \$(stat -nf %#Lp TD)" \
				  "$D" 0 "$D is $(printf %#o $D)" # 3 6 9 12 ...
	    done
	done

	# Now add a few more tests with less regular u/g/m masks
	# In here, include tests where umask value has no leading '0'

	# 10 loops, the same 3 tests in each loop, 30 more subtests
	# from 193 .. 222

	#        193 196 199  202 205 208 211  214  217 220
	for T in 013 047 722 0772 027 123 421 0124 0513 067
	do
		D=$(( 0777 & ~ 0$T ))
		F=$(( $D & ~ 0111 ))

		{ chmod +rwx TF TFT TD; rm -fr TF TFT TD; } 2>/dev/null || :

		check \
		  "umask $T; > TF; printf %d \$(stat -nf %#Lp TF)" \
				  "$F" 0 "$F is $(printf %#o $F)"	# +0

		check \
		  "umask $T; touch TFT; printf %d \$(stat -nf %#Lp TFT)" \
				  "$F" 0 "$F is $(printf %#o $F)"	# +1

		check \
		  "umask $T; mkdir TD; printf %d \$(stat -nf %#Lp TD)" \
				  "$D" 0 "$D is $(printf %#o $D)"	# +2
	done

	results
}

atf_test_case unset
unset_head() {
	atf_set descr "Tests the sh builtin 'unset'"
}
unset_body() {
	have_builtin unset || return 0

	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c '
		X=foo
		test "${X-bar}" = foo || exit 10
		unset X
		test "${X-bar}" = bar || exit 11
		unset X'
}

atf_test_case hash
hash_head() {
	atf_set descr "Tests the sh builtin 'hash' (ash extension)"
}
hash_body() {
	have_builtin hash || return 0
}

atf_test_case jobid
jobid_head() {
	atf_set descr "Tests sh builtin 'jobid' (NetBSD extension)"
}
jobid_body() {

	have_builtin jobid '' ':&' ';wait' || return 0

	# When the jobid command exists, it should work

	atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
	    ':& P=$!; J=$(jobid $!) || exit; wait; [ "$P" = "$J" ] && printf OK'

	# But not from a grandchild
	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
	    ':& P=$!; ( J=$(jobid $!) ) || exit; wait'

	# The first job created by a new shell should be %1
	atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
	    ':& P=$!; J=$(jobid %1) || exit; wait; [ "$P" = "$J" ] && printf OK'

	# And the next should be %2
	atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
	    ':& P1=$!; :& P2=$!; J=$(jobid %2) || exit; wait;
		[ "$P2" = "$J" ] && printf OK'

	# for a pipe, all the pids (2 in this case) should be printed
	atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
	    ': | true & P=$!; jobid %1 | { read X Y Z;
		{ test "$X" = "$P" && test -n "$Y" && test -z "$Z" ; } ||
		{ test "$Y" = "$P" && test -n "$X" && test -z "$Z" ; } } &&
			printf OK'

	# except if jobid -p works, we should get only the lead process
	have_builtin jobid '' ':&' '-p; wait' ' -p' &&
	    atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
		': | true & P=$!; jobid -p %1 | { read X Y Z;
		    test "$X" = "$P" && test -z "$Y" && test -z "$Z" ; } &&
		printf OK'

	return 0

	# If jobid -j exists, we should get the jobid (%1) back
	# But that requires -m enabled, and ATF won't allow that!
	have_builtin jobid '' ':&' '-j; wait' ' -j' && {
	    R=$(${TEST_SH} -c '( set -m; :& P=$!; J=$(jobid -j "$P") || exit; wait;
		    [ "$J" = "%1" ] && printf OK )')
	    X=$?
	    if [ "$X" -n0 0 ] || [ "$R" != "OK" ]
	    then
		atf-fail 'jobid -j failed'
	    fi
	}

	# For a simple command, the process group should be its PID
	# but only testable if the jobid command supports -g
	# These would also require -m in order to work
	have_builtin jobid '' 'set -m; :&' '-g; wait' ' -g' && {
	    # and only if sh job control is enabled, otherwise there is none
	    atf_check -s not-exit:0 -e empty -o inline:OK ${TEST_SH} -c \
		':& P=$!; J=$(jobid -g $!) || exit; wait;
		    [ "" = "$J" ] && printf OK'
	    # this test can't run under ATF, it kills anything run with -m
	    atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -m -c \
		':& P=$!; J=$(jobid -g $!) || exit; wait;
		    [ "$P" = "$J" ] && printf OK'
	}

	# Not allowed to return 1 (from have_builtin perhaps)
	return 0
}

atf_test_case let
let_head() {
	atf_set descr "Tests the sh builtin 'let' (common extension from ksh)"
}
let_body() {
	have_builtin let "" "" 1 || return 0
}

atf_test_case local
local_head() {
	atf_set descr "Tests the shell builtin 'local' (common extension)"
}
local_body() {
	have_builtin local "" "f () {" "X; }; f" || return 0

	atf_check -s exit:0 -e empty -o inline:OUTER ${TEST_SH} -c \
	    'f() { local VAR; VAR=$1; [ "$VAR" = INNER ] ; }
	     VAR=OUTER; f INNER || exit; printf %s "${VAR}"'
}

atf_test_case setvar
setvar_head() {
	atf_set descr "Tests the shell builtin 'setvar' (BSD extension)"
}
setvar_body() {
	have_builtin setvar || return 0

	atf_check -s exit:0 -e empty -o inline:foo ${TEST_SH} -c \
		'unset PQ && setvar PQ foo; printf %s "${PQ-not set}"'
	atf_check -s exit:0 -e empty -o inline:abcd ${TEST_SH} -c \
		'for x in a b c d; do setvar "$x" "$x"; done;
		 printf %s "$a$b$c$d"'
	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c \
		'a=1; b=2; c=3; d=4
		 for x in a b c d; do setvar "$x" ""; done;
		 printf %s "$a$b$c$d"'
}

atf_test_case fdflags
fdflags_head() {
	atf_set descr \
	   "Tests basic operation of sh builtin 'fdflags' (NetBSD extension)"
}
fdflags_body() {
	have_builtin fdflags || return 0
}

atf_test_case fdflags__s
fdflags__s_head() {
	atf_set descr "Checks setting/clearing flags on file descriptors"
}
fdflags__s_body() {
	have_builtin fdflags || return 0
}

atf_test_case fdflags__v
fdflags__v_head() {
	atf_set descr "Checks verbose operation of fdflags"
}
fdflags__v_body() {
	have_builtin fdflags || return 0
}

atf_test_case fdflags__v_s
fdflags__v_s_head() {
	atf_set descr "tests verbose operation of fdflags -s"
}
fdflags__v_s_body() {
	have_builtin fdflags || return 0
}

atf_test_case fdflags_multiple_fd
fdflags_multiple_fd_head() {
	atf_set descr "Checks operation of fdflags with more than one fd"
}
fdflags_multiple_fd_body() {
	have_builtin fdflags || return 0
}

atf_test_case fdflags_one_flag_at_a_time
fdflags_one_flag_at_a_time_head() {
	atf_set descr "Tests all possible fdflags flags, and combinations"
}
fdflags_one_flag_at_a_time_body() {
	have_builtin fdflags || return 0
}

atf_test_case fdflags_save_restore
fdflags_save_restore_head() {
	atf_set descr 'Verify that fd flags can be saved and restored'
}
fdflags_save_restore_body() {
	have_builtin fdflags || return 0
}

atf_test_case fdflags_names_abbreviated
fdflags_names_abbreviated_head() {
	atf_set descr 'Tests using abbreviated names for fdflags'
}
fdflags_names_abbreviated_body() {
	have_builtin fdflags || return 0
}

atf_test_case fdflags_xx_errors
fdflags_xx_errors_head() {
	atf_set descr 'Check various erroneous fdflags uses'
}
fdflags_xx_errors_body() {
	have_builtin fdflags || return 0
}

trap_head() {
	atf_set descr 'Check operation of trap command (not traps themselves)'
}
trap_body() {
	have_builtin trap || return 0

	atf_require_prog printf

	# We do need to make sure that the trap command does something.
	atf_check -s exit:0 -o inline:DONE -e empty ${TEST_SH} -c '
		trap "printf DONE" EXIT'
	# and that it can be undone.
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
		trap "printf DONE" EXIT; trap -- - EXIT'
	# and last that the trap command prints changed traps
	# (note that no particular format is required, but these elements are)
	atf_check -s exit:0 -o match:'printf OK' -o match:EXIT -e empty \
	    ${TEST_SH} -c 'trap "printf OK" EXIT; trap; trap -- - EXIT'

	# Next check that we can read back what trap sets (likely format)
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
		C="printf FAIL"
		trap "${C}" EXIT
		case "$(trap -p EXIT)" in "trap -- '"'\${C}'"' EXIT");;
		*) printf "FAIL: "; trap -p EXIT;; esac
		trap -- - EXIT'

	# And in executable form as well (an extension to the standard)
	have_builtin trap '' '' '-P EXIT' ' -P' &&
	   atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
		C="printf FAIL"
		trap "${C}" EXIT
		[ "${C}" = "$(trap -P EXIT)" ] || exit
		trap -- - EXIT'

	# Then $(trap) should return the changed traps (none here)
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
		[ -z "$(trap)" ]'
	# but not this time
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
		C="printf FAIL"
		trap "${C}" EXIT
		[ -z "$(trap)" ] && exit
		trap -- - EXIT'
	# an eval "$(trap)" should restore things
	atf_check -s exit:0 -o inline:OK -e empty ${TEST_SH} -c '
		C="printf OK"
		trap "${C}" EXIT
		T="$(trap)"
			trap -- - EXIT
		eval "${T}"'
	# but that fails when done the other way $T is empty.
	atf_check -s exit:0 -o inline:OK -e empty ${TEST_SH} -c '
		C="printf OK"
		trap -- - EXIT
		T="$(trap)"
			trap "${C}" EXIT
		eval "${T}"'
	# but when trap -p is used, it doesn't
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
		C="printf OK"
		trap -- - EXIT
		T="$(trap -p)"
			trap "${C}" EXIT
		eval "${T}"'

	# that $(trap) works (parent shell seen from subshell
	# has been tested many times above, but it should not
	# work after any other trap command has run
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
		C="printf FAIL"
		trap "${C}" EXIT
		printf %s "$( trap -- - INT ; trap)"
		trap -- - EXIT'
	# it also should not work if run in a subshell of a subshell
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
		C="printf FAIL"
		trap "${C}" EXIT
		printf %s "$( T=$(trap); printf %s '\"\$T\"' )"
		trap -- - EXIT'
	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
		C="printf FAIL"
		trap "${C}" EXIT
		( printf %s "$(trap)" )
		trap -- - EXIT'
	# but we should be able to see a subshell's traps in that subshell
	atf_check -s exit:0 -o match:SUB:DONE -e inline:SUB:DONE ${TEST_SH} -c '
		C="printf FAIL"
		trap "${C}" EXIT
		( trap "printf SUB:DONE >&2" EXIT; trap )
		trap -- - EXIT'
	atf_check -s exit:0 -o match:SUB:DONE -e inline:SUB:DONE ${TEST_SH} -c '
		C="printf FAIL"
		trap "${C}" EXIT
		( trap "printf SUB:DONE >&2" EXIT; printf %s "$(trap)" )
		trap -- - EXIT'
}


atf_init_test_cases() {

	# "standard" builtin commands in sh

	# no tests of the "very special" (almost syntax) builtins
	#  (break/continue/return) - they're tested enough elsewhere

	atf_add_test_case cd_pwd
	atf_add_test_case colon
	atf_add_test_case echo
	atf_add_test_case eval
	atf_add_test_case exec
	atf_add_test_case export
	atf_add_test_case getopts
	atf_add_test_case jobs
	atf_add_test_case read
	atf_add_test_case readonly
	atf_add_test_case trap
	atf_add_test_case true_false
	atf_add_test_case type
	atf_add_test_case ulimit
	atf_add_test_case umask
	atf_add_test_case unset

	# exit/wait/set/shift/alias/unalias/. should have their own tests
	# the test of trap tests only the command, not its function,
	# that needs to be tested elsewhere.
	# fc/times/fg/bg/%    are too messy to contemplate for now
	# command ??  (probably should have some tests)

	# Note that builtin versions of, printf, kill, ... are tested separately
	# (these are all "optional" builtins)
	# (echo is tested here because NetBSD sh builtin echo and /bin/echo
	#  are different)

	atf_add_test_case export_nbsd
	atf_add_test_case hash
	atf_add_test_case jobid
	atf_add_test_case let
	atf_add_test_case local
	atf_add_test_case readonly_nbsd
	atf_add_test_case setvar
	# inputrc should probably be tested in libedit tests (somehow)

	# fdflags has a bunch of test cases

	# Always run one test, so we get at least "skipped" result
	atf_add_test_case fdflags

	# but no need to say "skipped" lots more times...
	have_builtin fdflags available && {
		atf_add_test_case fdflags__s
		atf_add_test_case fdflags__v
		atf_add_test_case fdflags__v_s
		atf_add_test_case fdflags_multiple_fd
		atf_add_test_case fdflags_names_abbreviated
		atf_add_test_case fdflags_one_flag_at_a_time
		atf_add_test_case fdflags_save_restore
		atf_add_test_case fdflags_xx_errors
	}
	return 0
}
