t_realpath.sh revision 1.1 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