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