Home | History | Annotate | Line # | Download | only in realpath
      1 # $NetBSD: t_realpath.sh,v 1.1 2022/07/21 09:52:49 kre Exp $
      2 #
      3 # Copyright (c) 2022 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 
     28 # ===========================================================
     29 #
     30 # Test data and expected results
     31 
     32 # Note that the empty line calls realpath with no file arg
     33 existing='.
     34 
     35 ../Dir/StdOut
     36 ./S1
     37 ./S1/../S4
     38 ./Snr/../S4
     39 S1/S2/File
     40 S1/S3/Link
     41 Snr/HoHo
     42 Snr/Link
     43 L
     44 /
     45 /bin
     46 Self
     47 Self/
     48 S4/S1/'
     49 
     50 exist_results='Dir
     51 Dir
     52 Dir/StdOut
     53 Dir/S1
     54 Dir/S4
     55 Dir/S4
     56 Dir/S1/S2/File
     57 Dir/S1/S2/File
     58 Dir/Snr/HoHo
     59 Dir/S1
     60 Dir/StdOut
     61 /
     62 /bin
     63 Dir
     64 Dir
     65 Dir/S1'
     66 
     67 exist_root_only='Snx/HaHa
     68 Snx/Link'
     69 
     70 exist_root_results='Dir/Snx/HaHa
     71 Dir/S1/S2/File'
     72 
     73 nofile='-
     74 trash
     75 Snr/Haha
     76 T1
     77 T2
     78 T3
     79 T4
     80 T5
     81 ../Dir/T2
     82 ../Dir/T3
     83 /nonsense
     84 /bin/../nonsense
     85 ./Self/Self/Self/Self/S1/../Self/../Dir/Self/T1
     86 Self/nonsense'
     87 
     88 nofile_results='Dir/-
     89 Dir/trash
     90 Dir/Snr/Haha
     91 Dir/NoSuchFile
     92 Dir/S1/NoSuchFile
     93 Dir/S1/NoSuchFile
     94 Dir/S1/S2/NoSuchFile
     95 Dir/S1/S2/NoSuchFile
     96 Dir/S1/NoSuchFile
     97 Dir/S1/NoSuchFile
     98 /nonsense
     99 /nonsense
    100 Dir/NoSuchFile
    101 Dir/nonsense'
    102 
    103 always_fail='StdOut/
    104 StdOut/../StdErr
    105 Loop
    106 S1/S5/Link
    107 Loop/../StdOut
    108 BigLoop
    109 U1
    110 U2
    111 U3
    112 U4
    113 U5
    114 U6
    115 U7
    116 U8
    117 U9
    118 T1/NoSuchFile
    119 T1/../NoSuchFile
    120 U9/../NoSuchFile
    121 U9/../StdOut'
    122 
    123 
    124 # ===========================================================
    125 # Helper functions
    126 #
    127 
    128 # Create the test environment
    129 setup()
    130 {
    131 	atf_require_prog /usr/bin/mktemp
    132 	atf_require_prog /bin/ln
    133 	atf_require_prog /bin/cp
    134 	atf_require_prog /bin/mkdir
    135 	atf_require_prog /bin/chmod
    136 
    137 	DIR=${PWD}/$(mktemp -d Dir.XXXXX) ||
    138 		atf_fail "Did not make test directory"
    139 	cd "${DIR}" || atf_fail "Unable to cd $DIR"
    140 
    141 	ID=$( set -- $( type id ) && test "$1" = id && test "$2" = is &&
    142 		test $# -eq 3 && printf %s "$3"  || printf no-id-program)
    143 
    144 	mkdir Dir && cd Dir			|| atf_fail "enter Dir"
    145 
    146 	>StdOut					|| atf_fail "setup StdOut"
    147 	>StdErr					|| atf_fail "setup StdErr"
    148 	ln -s ../Dir Dir			|| atf_fail "setup Dir"
    149 	ln -s Loop Loop				|| atf_fail "setup Loop"
    150 	ln -s . Self				|| atf_fail "setup Self"
    151 	mkdir S1 S1/S2 S1/S3 S4 S4/S5		|| atf_fail "setup subdirs"
    152 	echo S1/S2/File > S1/S2/File		|| atf_fail "setup File"
    153 	ln -s ../S2/File S1/S3/Link		|| atf_fail "setup S3/Link"
    154 	ln -s ../S1 S4/S1			|| atf_fail "setup S4/S1"
    155 	ln -s StdOut L1				|| atf_fail "setup L1"
    156 	ln -s L1 L2				|| atf_fail "setup L2"
    157 	ln -s ../L2 S1/L3			|| atf_fail "setup L3"
    158 	ln -s ../L3 S1/S2/L4			|| atf_fail "setup L4"
    159 	ln -s ../S2/L4 S1/S3/L5			|| atf_fail "setup L5"
    160 	ln -s S1/S3/L5 L			|| atf_fail "setup L"
    161 	ln -s ${PWD}/S1 S4/PWDS1		|| atf_fail "setup PWDS1"
    162 	ln -s ${PWD}/S9 S4/PWDS9		|| atf_fail "setup PWDS9"
    163 	ln -s ${PWD}/S9/File S4/PWDS9F		|| atf_fail "setup PWDS9F"
    164 	ln -s ../S4/BigLoop S1/BigLoop		|| atf_fail "setup S1/BigLoop"
    165 	ln -s ../BigLoop S4/BigLoop		|| atf_fail "setup S4/BigLoop"
    166 	ln -s "${DIR}"/Dir/S1/BigLoop BigLoop	|| atf_fail "setup BigLoop"
    167 	mkdir Snx				|| atf_fail "setup Snx"
    168 	cp /dev/null Snx/HaHa			|| atf_fail "setup Snx/HaHa"
    169 	ln -s "${DIR}"/Dir/S1/S2/File Snx/Link	|| atf_fail "setup Snx/Link"
    170 	mkdir Snr				|| atf_fail "setup Snr"
    171 	cp /dev/null Snr/HoHo			|| atf_fail "setup Snr/HoHo"
    172 	ln -s "${DIR}"/Dir/S4/PWDS1 Snr/Link	|| atf_fail "setup Snr/Link"
    173 	ln -s ../Snx/HaHa Snr/HaHa		|| atf_fail "setup HaHa"
    174 	ln -s "${DIR}"/Dir/NoSuchFile T1	|| atf_fail "setup T1"
    175 	ln -s "${DIR}"/Dir/S1/NoSuchFile T2	|| atf_fail "setup T2"
    176 	ln -s S1/NoSuchFile T3			|| atf_fail "setup T3"
    177 	ln -s "${DIR}"/Dir/S1/S2/NoSuchFile T4	|| atf_fail "setup T4"
    178 	ln -s S1/S2/NoSuchFile T5		|| atf_fail "setup T5"
    179 	ln -s "${DIR}"/Dir/StdOut/CannotExist T6 || atf_fail "setup T6"
    180 	ln -s "${DIR}"/Dir/NoDir/WhoKnows U1	|| atf_fail "setup U1"
    181 	ln -s "${DIR}"/Dir/S1/NoDir/WhoKnows U2	|| atf_fail "setup U2"
    182 	ln -s "${DIR}"/Dir/S1/S2/NoDir/WhoKnows U3 || atf_fail "setup U3"
    183 	ln -s "${DIR}"/Dir/S1/../NoDir/WhoKnows U4 || atf_fail "setup U4"
    184 	ln -s "${DIR}"/Dir/NoDir/../StdOut U5	|| atf_fail "setup U5"
    185 	ln -s NoDir/../StdOut U6		|| atf_fail "setup U6"
    186 	ln -s S1/NoDir/../../StdOut U7		|| atf_fail "setup U7"
    187 	ln -s "${DIR}"/Dir/Missing/NoDir/WhoKnows U8 || atf_fail "setup U8"
    188 	ln -s "${DIR}"/Dir/Missing/NoDir/../../StdOut U9 || atf_fail "setup U9"
    189 	chmod a+r,a-x Snx			|| atf_fail "setup a-x "
    190 	chmod a+x,a-r Snr			|| atf_fail "setup a-r"
    191 }
    192 
    193 # ATF will remove all the files we made, just ensure perms are OK
    194 cleanup()
    195 {
    196 	chmod -R u+rwx .
    197 	return 0
    198 }
    199 
    200 run_tests_pass()
    201 {
    202 	opt=$1
    203 	tests=$2
    204 	results=$3
    205 
    206 	FAILS=
    207 	FAILURES=0
    208 	T=0
    209 
    210 	while [ "${#tests}" -gt 0 ]
    211 	do
    212 		FILE=${tests%%$'\n'*}
    213 		EXP=${results%%$'\n'*}
    214 
    215 		tests=${tests#"${FILE}"};	tests=${tests#$'\n'}
    216 		results=${results#"${EXP}"};	results=${results#$'\n'}
    217 
    218 		test -z "${EXP}" && atf_fail "Too few results (test botch)"
    219 
    220 		T=$(( $T + 1 ))
    221 
    222 		GOT=$(realpath $opt -- ${FILE:+"${FILE}"})
    223 		STATUS=$?
    224 
    225 		case "${GOT}" in
    226 		'')	;;		# nothing printed, deal with that below
    227 
    228 		/*)			# Full Path (what we want)
    229 			# Remove the unpredictable ATF dir prefix (if present)
    230 			GOT=${GOT#"${DIR}/"}
    231 			# Now it might be a relative path, that's OK
    232 			# at least it can be compared (its prefix is known)
    233 			;;
    234 
    235 		*)			# a relative path was printed
    236 			FAILURES=$(($FAILURES + 1))
    237 			FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T:"
    238 			FAILS="${FAILS}${opt:+ $opt} '${FILE}'"
    239 			FAILS="${FAILS}: output relative path '${GOT}'"
    240 			FAILS="${FAILS}, and exit($STATUS)"
    241 			continue
    242 			;;
    243 		esac
    244 
    245 
    246 		if [ $STATUS -ne 0 ] || [ "${EXP}" != "${GOT}" ]
    247 		then
    248 			FAILURES=$(($FAILURES + 1))
    249 			if [ $STATUS -ne 0 ]
    250 			then
    251 			    FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T:"
    252 			    FAILS="${FAILS}${opt:+ $opt} '${FILE}'"
    253 			    FAILS="${FAILS} failed: status ${STATUS}"
    254 			else
    255 			    FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T:"
    256 			    FAILS="${FAILS}${opt:+ $opt} '${FILE}'"
    257 			    FAILS="${FAILS} expected '${EXP}' received '${GOT}'"
    258 			fi
    259 		fi
    260 	done
    261 
    262 	if test  -n "${results}"
    263 	then
    264 		FAILURES=$(( $FAILURES + 1 ))
    265 
    266 		N=$(( $(printf '%s\n' "${results}" | wc -l) ))
    267 		s=s; if [ $N -eq 1 ]; then s=; fi
    268 		FAILS=${FAILS:+"${FAILS}"$'\n'}"After $T tests"
    269 		FAILS="still $N more result$s (test botch)"
    270 	fi
    271 
    272 	if [ $FAILURES -gt 0 ]
    273 	then
    274 		s=s
    275 		if [ $FAILURES -eq 1 ]; then s=; fi
    276 		printf >&2 '%d path%s resolved incorrectly:\n%s\n' \
    277 			"$FAILURES" "$s" "${FAILS}"
    278 		atf_fail "$FAILURES path$s resolved incorrectly; see stderr"
    279 	fi
    280 	return 0
    281 }
    282 
    283 run_tests_fail()
    284 {
    285 	opt=$1
    286 	tests=$2
    287 
    288 	FAILS=
    289 	FAILURES=0
    290 	T=0
    291 
    292 	while [ "${#tests}" -gt 0 ]
    293 	do
    294 		FILE=${tests%%$'\n'*}
    295 
    296 		tests=${tests#"${FILE}"};	tests=${tests#$'\n'}
    297 
    298 		test -z "${FILE}" && continue
    299 
    300 		T=$(( $T + 1 ))
    301 
    302 		GOT=$(realpath $opt -- "${FILE}" 2>StdErr)
    303 		STATUS=$?
    304 
    305 		ERR=$(cat StdErr)
    306 
    307 		if [ $STATUS -eq 0 ] || [ "${GOT}" ] || ! [ "${ERR}" ]
    308 		then
    309 			FAILURES=$(($FAILURES + 1))
    310 			if [ "${STATUS}" -eq 0 ]
    311 			then
    312 				FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T: "
    313 				FAILS="${FAILS}${opt:+ $opt} '${FILE}' worked;"
    314 				FAILS="${FAILS} received: '${GOT}'}"
    315 
    316 				if [ "${ERR}" ]; then
    317 					FAILS="${FAILS} and on stderr '${ERR}'"
    318 				fi
    319 			elif [ "${GOT}" ]
    320 			then
    321 				FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T:"
    322 				FAILS="${FAILS}${opt:+ $opt} '${FILE}' failed,"
    323 				FAILS="${FAILS} but with '${GOT}' on stdout"
    324 
    325 				if [ "${ERR}" ]; then
    326 					FAILS="${FAILS}, and on stderr '${ERR}'"
    327 				else
    328 					FAILS="${FAILS}, and empty stderr"
    329 				fi
    330 			else
    331 				FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T:"
    332 				FAILS="${FAILS}${opt:+ $opt} '${FILE}' failed,"
    333 				FAILS="${FAILS} but with no error message"
    334 			fi
    335 		fi
    336 	done
    337 	if [ $FAILURES -gt 0 ]
    338 	then
    339 		S=s
    340 		if [ $FAILURES -eq 1 ]; then s=; fi
    341 		printf >&2 '%d path%s resolved incorrectly:\n%s\n' \
    342 			"$FAILURES" "$s" "${FAILS}"
    343 		atf_fail "$FAILURES path$s resolved incorrectly; see stderr"
    344 	fi
    345 	return 0
    346 }
    347 
    348 # ===================================================================
    349 # Test cases setup follows (but almost all the work is earlier)
    350 
    351 atf_test_case a__e_ok cleanup
    352 realpath_e_ok_head()
    353 {
    354 	atf_set descr "Test realpath (with -e) cases which should work"
    355 }
    356 a__e_ok_body()
    357 {
    358 	setup
    359 	run_tests_pass -e "${existing}" "${exist_results}"
    360 
    361 	if [ -x "${ID}" ] && [ "$("$ID" -u)" = 0 ]
    362 	then
    363 		run_tests_pass -e "${exist_root_only}" "${exist_root_results}"
    364 	fi
    365 }
    366 a__e_ok_cleanup()
    367 {
    368 	cleanup
    369 }
    370 
    371 atf_test_case b__E_ok cleanup
    372 b__E_ok_head()
    373 {
    374 	atf_set descr "Test realpath (with -E) cases which should work"
    375 }
    376 b__E_ok_body() {
    377 	setup
    378 	# everything which works with -e should also work with -E
    379 	run_tests_pass -E "${existing}" "${exist_results}"
    380 	run_tests_pass -E "${nofile}" "${nofile_results}"
    381 
    382 	if [ -x "${ID}" ] && [ "$("${ID}" -u)" = 0 ]
    383 	then
    384 		run_tests_pass -E "${exist_root_only}" "${exist_root_results}"
    385 	fi
    386 }
    387 b__E_ok_cleanup()
    388 {
    389 	cleanup
    390 }
    391 
    392 atf_test_case c__ok cleanup
    393 c__ok_head()
    394 {
    395 	atf_set descr "Test realpath (without -e or -E) cases which should work"
    396 }
    397 c__ok_body() {
    398 	setup
    399 	# Our default for realpath is -E, so the -E tests should work
    400 	run_tests_pass '' "${existing}" "${exist_results}"
    401 	# but more should work as well
    402 	run_tests_pass '' "${nofile}" "${nofile_results}"
    403 
    404 	if [ -x "${ID}" ] && [ "$("${ID}" -u)" = 0 ]
    405 	then
    406 		run_tests_pass '' "${exist_root_only}" "${exist_root_results}"
    407 	fi
    408 }
    409 c__ok_cleanup()
    410 {
    411 	cleanup
    412 }
    413 
    414 atf_test_case d__E_fail
    415 d__E_fail_head()
    416 {
    417 	atf_set descr "Test realpath -e cases which should not work"
    418 }
    419 d__E_fail_body()
    420 {
    421 	setup
    422 	run_tests_fail -E "${always_fail}"
    423 	if [ -x "${ID}" ] && [ "$("${ID}" -u)" != 0 ]
    424 	then
    425 		run_tests_fail -E "${exist_root_only}"
    426 	fi
    427 }
    428 d__E_fail_cleanup()
    429 {
    430 	cleanup
    431 }
    432 
    433 atf_test_case e__e_fail
    434 e__e_fail_head()
    435 {
    436 	atf_set descr "Test realpath -e cases which should not work"
    437 }
    438 e__e_fail_body()
    439 {
    440 	setup
    441 	# Some -E tests that work should fail with -e
    442 	run_tests_fail -e "${nofile}"
    443 	run_tests_fail -e "${always_fail}"
    444 	if [ -x "${ID}" ] && [ "$("${ID}" -u)" != 0 ]
    445 	then
    446 		run_tests_fail -e "${exist_root_only}"
    447 	fi
    448 }
    449 e__e_fail_cleanup()
    450 {
    451 	cleanup
    452 }
    453 
    454 atf_test_case f__fail
    455 f__fail_head()
    456 {
    457 	atf_set descr "Test realpath cases which should not work (w/o -[eE])"
    458 }
    459 f__fail_body()
    460 {
    461 	setup
    462 	run_tests_fail '' "${always_fail}"
    463 	if [ -x "${ID}" ] && [ "$("${ID}" -u)" != 0 ]
    464 	then
    465 		run_tests_fail '' "${exist_root_only}"
    466 	fi
    467 }
    468 f__fail_cleanup()
    469 {
    470 	cleanup
    471 }
    472 
    473 atf_test_case g__q cleanup
    474 g__q_head()
    475 {
    476 	atf_set descr "Test realpath's -q option; also test usage message"
    477 }
    478 g__q_body()
    479 {
    480 	setup
    481 
    482 	# Just run these tests here, the paths have been tested
    483 	# already, all we care about is that -q suppresses err messages
    484 	# about the ones that fail, so just test those.  Since those
    485 	# always fail, it is irrlevant which of -e or -E we would use,
    486 	# so simply use neither.
    487 
    488 	# This is adapted from run_tests_fail
    489 
    490 	FAILURES=0
    491 	FAILS=
    492 
    493 	opt=-q
    494 
    495 	T=0
    496 	for FILE in ${always_fail}
    497 	do
    498 
    499 		test -z "${FILE}" && continue
    500 
    501 		T=$(( $T + 1 ))
    502 
    503 		GOT=$(realpath $opt -- "${FILE}" 2>StdErr)
    504 		STATUS=$?
    505 
    506 		ERR=$(cat StdErr)
    507 
    508 		if [ $STATUS -eq 0 ] || [ "${GOT}" ] || [ "${ERR}" ]
    509 		then
    510 			FAILURES=$(($FAILURES + 1))
    511 			if [ "${STATUS}" -eq 0 ]
    512 			then
    513 				FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T: "
    514 				FAILS="${FAILS}${opt:+ $opt} '${FILE}' worked;"
    515 				FAILS="${FAILS} received: '${GOT}'}"
    516 
    517 				if [ "${ERR}" ]; then
    518 					FAILS="${FAILS} and on stderr '${ERR}'"
    519 				fi
    520 			elif [ "${GOT}" ]
    521 			then
    522 				FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T:"
    523 				FAILS="${FAILS}${opt:+ $opt} '${FILE}' failed,"
    524 				FAILS="${FAILS} but with '${GOT}' on stdout"
    525 
    526 				if [ "${ERR}" ]; then
    527 					FAILS="${FAILS}, and on stderr '${ERR}'"
    528 				else
    529 					FAILS="${FAILS}, and empty stderr"
    530 				fi
    531 			else
    532 				FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T:"
    533 				FAILS="${FAILS}${opt:+ $opt} '${FILE}' failed,"
    534 				FAILS="${FAILS} stderr: '${ERR}'"
    535 			fi
    536 		fi
    537 	done
    538 
    539 	# There are a couple of cases where -q does not suppress stderr
    540 
    541 	for FILE in '' -wObBl@ --
    542 	do
    543 
    544 		T=$(( $T + 1 ))
    545 
    546 		unset XTRA
    547 		case "${FILE}" in
    548 		'')	;;
    549 		--)	XTRA=;;
    550 		-*)	XTRA=/junk;;
    551 		esac
    552 
    553 		# Note lack of -- in the following, so $FILE can be either
    554 		# a file name (well, kind of...), or options.
    555 
    556 		GOT=$(realpath $opt "${FILE}" ${XTRA+"${XTRA}"} 2>StdErr)
    557 		STATUS=$?
    558 
    559 		ERR=$(cat StdErr)
    560 
    561 		if [ $STATUS -eq 0 ] || [ "${GOT}" ] || ! [ "${ERR}" ]
    562 		then
    563 			FAILURES=$(($FAILURES + 1))
    564 			if [ "${STATUS}" -eq 0 ]
    565 			then
    566 				FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T: "
    567 				FAILS="${FAILS}${opt:+ $opt} ${FILE:-''}"
    568 				FAILS="${FAILS}${XTRA:+ $XTRA} worked;"
    569 				FAILS="${FAILS} received: '${GOT}'}"
    570 
    571 				if [ "${ERR}" ]; then
    572 					FAILS="${FAILS} and on stderr '${ERR}'"
    573 				fi
    574 			elif [ "${GOT}" ]
    575 			then
    576 				FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T:"
    577 				FAILS="${FAILS}${opt:+ $opt} ${FILE:-''}"
    578 				FAILS="${FAILS}${XTRA:+ ${XTRA}} failed,"
    579 				FAILS="${FAILS} but with '${GOT}' on stdout"
    580 
    581 				if [ "${ERR}" ]; then
    582 					FAILS="${FAILS}, and on stderr '${ERR}'"
    583 				else
    584 					FAILS="${FAILS}, and empty stderr"
    585 				fi
    586 			else
    587 				FAILS=${FAILS:+"${FAILS}"$'\n'}"Path $T:"
    588 				FAILS="${FAILS}${opt:+ $opt} ${FILE:-''}"
    589 				FAILS="${FAILS}${XTRA:+ ${XTRA}} failed,"
    590 				FAILS="${FAILS} with stderr empty"
    591 			fi
    592 		fi
    593 	done
    594 
    595 	if [ $FAILURES -gt 0 ]
    596 	then
    597 		s=s
    598 		if [ $FAILURES -eq 1 ]; then s=; fi
    599 		printf >&2 '%d path%s resolved incorrectly:\n%s\n' \
    600 			"$FAILURES" "$s" "${FAILS}"
    601 		atf_fail "$FAILURES path$s resolved incorrectly; see stderr"
    602 	fi
    603 	return 0
    604 }
    605 g__q_cleanup()
    606 {
    607 	cleanup
    608 }
    609 
    610 atf_test_case h__n_args
    611 h__n_args_head()
    612 {
    613 	atf_set descr "Test realpath with multiple file args"
    614 }
    615 h__n_args_body()
    616 {
    617 	setup
    618 
    619 	# Since these paths have already (hopefully) tested and work
    620 	# (if a__e_ok had any failures, fix those before even looking
    621 	# at any failure here)
    622 
    623 	# Since we are assuming that the test cases all work, simply
    624 	# Count how many there are, and then expect the same number
    625 	# of answers
    626 
    627 	unset IFS
    628 	set -- ${existing}
    629 	# Note that any empty line (no args) case just vanished...
    630 	# That would be meaningless here, removing it is correct.
    631 
    632  	GOT=$(realpath -e -- "$@" 2>StdErr)
    633 	STATUS=$?
    634 
    635 	ERR=$(cat StdErr; printf X)
    636 	ERR=${ERR%X}
    637 
    638 	NR=$(( $(printf '%s\n' "${GOT}" | wc -l) ))
    639 
    640 	if [ $NR -ne $# ] || [ $STATUS -ne 0 ] || [ -s StdErr ]
    641 	then
    642 		printf >&2 'Stderr from test:\n%s\n' "${ERR}"
    643 		if [ $STATUS -eq 0 ]; then S="OK"; else S="FAIL($STATUS)"; fi
    644 		if [ ${#ERR} -ne 0 ]
    645 		then
    646 			E="${#ERR} bytes on stderr"
    647 		else
    648 			E="nothing on stderr"
    649 		fi
    650 		atf_fail 'Given %d args, got %d results; Status:%s; %s\n' \
    651 			"$#" "${NR}" "${S}" "${E}"
    652 	fi
    653 	return 0
    654 }
    655 h__n_args_cleanup()
    656 {
    657 	cleanup
    658 }
    659 
    660 atf_init_test_cases()
    661 {
    662 	atf_add_test_case a__e_ok
    663 	atf_add_test_case b__E_ok
    664 	atf_add_test_case c__ok
    665 	atf_add_test_case d__E_fail
    666 	atf_add_test_case e__e_fail
    667 	atf_add_test_case f__fail
    668 	atf_add_test_case g__q
    669 	atf_add_test_case h__n_args
    670 }
    671