xref: /netbsd-src/tests/usr.bin/realpath/t_realpath.sh (revision fa7d65a3b62a9da33e5e8b742de084ec8351e8c0)
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
33existing='.
34
35../Dir/StdOut
36./S1
37./S1/../S4
38./Snr/../S4
39S1/S2/File
40S1/S3/Link
41Snr/HoHo
42Snr/Link
43L
44/
45/bin
46Self
47Self/
48S4/S1/'
49
50exist_results='Dir
51Dir
52Dir/StdOut
53Dir/S1
54Dir/S4
55Dir/S4
56Dir/S1/S2/File
57Dir/S1/S2/File
58Dir/Snr/HoHo
59Dir/S1
60Dir/StdOut
61/
62/bin
63Dir
64Dir
65Dir/S1'
66
67exist_root_only='Snx/HaHa
68Snx/Link'
69
70exist_root_results='Dir/Snx/HaHa
71Dir/S1/S2/File'
72
73nofile='-
74trash
75Snr/Haha
76T1
77T2
78T3
79T4
80T5
81../Dir/T2
82../Dir/T3
83/nonsense
84/bin/../nonsense
85./Self/Self/Self/Self/S1/../Self/../Dir/Self/T1
86Self/nonsense'
87
88nofile_results='Dir/-
89Dir/trash
90Dir/Snr/Haha
91Dir/NoSuchFile
92Dir/S1/NoSuchFile
93Dir/S1/NoSuchFile
94Dir/S1/S2/NoSuchFile
95Dir/S1/S2/NoSuchFile
96Dir/S1/NoSuchFile
97Dir/S1/NoSuchFile
98/nonsense
99/nonsense
100Dir/NoSuchFile
101Dir/nonsense'
102
103always_fail='StdOut/
104StdOut/../StdErr
105Loop
106S1/S5/Link
107Loop/../StdOut
108BigLoop
109U1
110U2
111U3
112U4
113U5
114U6
115U7
116U8
117U9
118T1/NoSuchFile
119T1/../NoSuchFile
120U9/../NoSuchFile
121U9/../StdOut'
122
123
124# ===========================================================
125# Helper functions
126#
127
128# Create the test environment
129setup()
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
194cleanup()
195{
196	chmod -R u+rwx .
197	return 0
198}
199
200run_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
283run_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
351atf_test_case a__e_ok cleanup
352realpath_e_ok_head()
353{
354	atf_set descr "Test realpath (with -e) cases which should work"
355}
356a__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}
366a__e_ok_cleanup()
367{
368	cleanup
369}
370
371atf_test_case b__E_ok cleanup
372b__E_ok_head()
373{
374	atf_set descr "Test realpath (with -E) cases which should work"
375}
376b__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}
387b__E_ok_cleanup()
388{
389	cleanup
390}
391
392atf_test_case c__ok cleanup
393c__ok_head()
394{
395	atf_set descr "Test realpath (without -e or -E) cases which should work"
396}
397c__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}
409c__ok_cleanup()
410{
411	cleanup
412}
413
414atf_test_case d__E_fail
415d__E_fail_head()
416{
417	atf_set descr "Test realpath -e cases which should not work"
418}
419d__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}
428d__E_fail_cleanup()
429{
430	cleanup
431}
432
433atf_test_case e__e_fail
434e__e_fail_head()
435{
436	atf_set descr "Test realpath -e cases which should not work"
437}
438e__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}
449e__e_fail_cleanup()
450{
451	cleanup
452}
453
454atf_test_case f__fail
455f__fail_head()
456{
457	atf_set descr "Test realpath cases which should not work (w/o -[eE])"
458}
459f__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}
468f__fail_cleanup()
469{
470	cleanup
471}
472
473atf_test_case g__q cleanup
474g__q_head()
475{
476	atf_set descr "Test realpath's -q option; also test usage message"
477}
478g__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}
605g__q_cleanup()
606{
607	cleanup
608}
609
610atf_test_case h__n_args
611h__n_args_head()
612{
613	atf_set descr "Test realpath with multiple file args"
614}
615h__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}
655h__n_args_cleanup()
656{
657	cleanup
658}
659
660atf_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