1# 2# CDDL HEADER START 3# 4# The contents of this file are subject to the terms of the 5# Common Development and Distribution License (the "License"). 6# You may not use this file except in compliance with the License. 7# 8# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9# or http://www.opensolaris.org/os/licensing. 10# See the License for the specific language governing permissions 11# and limitations under the License. 12# 13# When distributing Covered Code, include this CDDL HEADER in each 14# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15# If applicable, add the following below this CDDL HEADER, with the 16# fields enclosed by brackets "[]" replaced with your own identifying 17# information: Portions Copyright [yyyy] [name of copyright owner] 18# 19# CDDL HEADER END 20# 21 22# 23# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. 24# Copyright 2017, Joyent, Inc. 25# 26 27# 28# Additional tests borrowed from ksh93 builtin tail test script 29# (usr/src/lib/libshell/common/tests/sun_solaris_builtin_tail). Modified 30# to use /usr/bin/tail rather than the ksh93 builtin. 31# 32TAIL=/usr/bin/tail 33 34while [[ $# -gt 0 ]]; do 35 case $1 in 36 -o) 37 TAIL=$(cd $(dirname $2); pwd)/$(basename $2) 38 shift 2 39 ;; 40 *) 41 echo "Usage: sun_solaris_tail.sh" \ 42 "[-o <override tail executable>]" 43 exit 1 44 ;; 45 esac 46done 47 48# test setup 49function err_exit 50{ 51 print -u2 -n "\t" 52 print -u2 -r ${Command}[$1]: "${@:2}" 53 (( Errors < 127 && Errors++ )) 54} 55alias err_exit='err_exit $LINENO' 56 57set -o nounset 58Command=${0##*/} 59integer Errors=0 60 61# common functions 62function isvalidpid 63{ 64 kill -0 ${1} 2>/dev/null && return 0 65 return 1 66} 67 68function waitpidtimeout 69{ 70 integer pid=$1 71 float timeout=$2 72 float i 73 float -r STEP=0.5 # const 74 75 (( timeout=timeout/STEP )) 76 77 for (( i=0 ; i < timeout ; i+=STEP )) ; do 78 isvalidpid ${pid} || break 79 sleep ${STEP} 80 done 81 82 return 0 83} 84 85function myintseq 86{ 87 integer i 88 float arg1=$1 89 float arg2=$2 90 float arg3=$3 91 92 case $# in 93 1) 94 for (( i=1 ; i <= arg1 ; i++ )) ; do 95 printf "%d\n" i 96 done 97 ;; 98 2) 99 for (( i=arg1 ; i <= arg2 ; i++ )) ; do 100 printf "%d\n" i 101 done 102 ;; 103 3) 104 for (( i=arg1 ; i <= arg3 ; i+=arg2 )) ; do 105 printf "%d\n" i 106 done 107 ;; 108 *) 109 print -u2 -f "%s: Illegal number of arguments %d\n" "$0" $# 110 return 1 111 ;; 112 esac 113 114 return 0 115} 116 117# quote input string but use single-backslash that "err_exit" prints 118# the strings correctly 119function singlebackslashquote 120{ 121 typeset s 122 s="$(printf "%q\n" "$1")" 123 print -r "$s" 124 return 0 125} 126 127# quote input string but use double-backslash that "err_exit" prints 128# the strings correctly 129function doublebackslashquote 130{ 131 typeset s 132 s="$(printf "%q\n" "$1")" 133 s="${s//\\/\\\\}" 134 print -r "$s" 135 return 0 136} 137 138 139# main 140builtin mktemp || err_exit "mktemp builtin not found" 141builtin rm || err_exit "rm builtin not found" 142# builtin tail || err_exit "tail builtin not found" 143 144typeset ocwd 145typeset tmpdir 146 147# create temporary test directory 148ocwd="$PWD" 149tmpdir="$(mktemp -t -d "test_sun_solaris_builtin_tail.XXXXXXXX")" || err_exit "Cannot create temporary directory" 150 151cd "${tmpdir}" || { err_exit "cd ${tmpdir} failed." ; exit $((Errors)) ; } 152 153 154# run tests: 155 156# test1: basic tests 157compound -a testcases=( 158 ( 159 name="reverse_n" 160 input=$'hello\nworld' 161 compound -A tail_args=( 162 [legacy]=( argv=( "-r" ) ) 163 ) 164 expected_output=$'world\nhello' 165 ) 166 ( 167 name="revlist0n" 168 input=$'1\n2\n3\n4' 169 compound -A tail_args=( 170 [legacy]=( argv=( "-0" ) ) 171# [std_like]=( argv=( "-n" "0" ) ) 172 ) 173 expected_output=$'' 174 ) 175 ( 176 name="revlist0nr" 177 input=$'1\n2\n3\n4' 178 compound -A tail_args=( 179 [legacy]=( argv=( "-0r" ) ) 180# [std_like]=( argv=( "-n" "0" "-r" ) ) 181# [long_options]=( argv=( "--lines" "0" "--reverse" ) ) 182 ) 183 expected_output=$'' ) 184 ( 185 name="revlist1n" 186 input=$'1\n2\n3\n4' 187 compound -A tail_args=( 188 [legacy]=( argv=( "-1" ) ) 189# [std_like]=( argv=( "-n" "1" ) ) 190# [long_options]=( argv=( "--lines" "1" ) ) 191 ) 192 expected_output=$'4' ) 193 ( 194 name="revlist1nr" 195 input=$'1\n2\n3\n4' 196 compound -A tail_args=( 197 [legacy]=( argv=( "-1r" ) ) 198# [std_like]=( argv=( "-n" "1" "-r" ) ) 199# [long_options]=( argv=( "--lines" "1" "--reverse" ) ) 200 ) 201 expected_output=$'4' 202 ) 203 ( 204 name="revlist2n" 205 input=$'1\n2\n3\n4' 206 compound -A tail_args=( 207 [legacy]=( argv=( "-2" ) ) 208# [std_like]=( argv=( "-n" "2" ) ) 209 ) 210 expected_output=$'3\n4' 211 ) 212 ( 213 name="revlist2nr" 214 input=$'1\n2\n3\n4' 215 compound -A tail_args=( 216 [legacy]=( argv=( "-2r" ) ) 217# [std_like]=( argv=( "-n" "2" "-r" ) ) 218 ) 219 expected_output=$'4\n3' 220 ) 221 ( 222 name="revlist3nr" 223 input=$'1\n2\n3\n4' 224 compound -A tail_args=( 225 [legacy]=( argv=( "-3r" ) ) 226# [std_like]=( argv=( "-n" "3" "-r" ) ) 227 ) 228 expected_output=$'4\n3\n2' 229 ) 230 ( 231 name="revlist2p" 232 input=$'1\n2\n3\n4' 233 compound -A tail_args=( 234 [legacy]=( argv=( "+2" ) ) 235# [std_like]=( argv=( "-n" "+2" ) ) 236 ) 237 expected_output=$'2\n3\n4' 238 ) 239# Note: following test case trips up legacy Solaris 'tail' as well 240# ( 241# name="revlist2pr" 242# input=$'1\n2\n3\n4' 243# compound -A tail_args=( 244# [legacy]=( argv=( "+2r" ) ) 245# [std_like]=( argv=( "-n" "+2" "-r" ) ) 246# ) 247# expected_output=$'4\n3\n2' 248# ) 249 ( 250 name="revlist3p" 251 input=$'1\n2\n3\n4' 252 compound -A tail_args=( 253 [legacy]=( argv=( "+3" ) ) 254 [std_like]=( argv=( "-n" "+3" ) ) 255 ) 256 expected_output=$'3\n4' 257 ) 258# Note: following test case trips up legacy Solaris 'tail' as well 259# ( 260# name="revlist3pr" 261# input=$'1\n2\n3\n4' 262# compound -A tail_args=( 263# [legacy]=( argv=( "+3r" ) ) 264# [std_like]=( argv=( "-n" "+3" "-r" ) ) 265# ) 266# expected_output=$'4\n3' 267# ) 268 ( 269 name="revlist4p" 270 input=$'1\n2\n3\n4' 271 compound -A tail_args=( 272 [legacy]=( argv=( "+4" ) ) 273# [std_like]=( argv=( "-n" "+4" ) ) 274 ) 275 expected_output=$'4' 276 ) 277# Note: following test case trips up legacy Solaris 'tail' as well 278# ( 279# name="revlist4pr" 280# input=$'1\n2\n3\n4' 281# compound -A tail_args=( 282# [legacy]=( argv=( "+4r" ) ) 283# [std_like]=( argv=( "-n" "+4" "-r" ) ) 284# ) 285# expected_output=$'4' 286# ) 287 ( 288 name="revlist5p" 289 input=$'1\n2\n3\n4' 290 compound -A tail_args=( 291 [legacy]=( argv=( "+5" ) ) 292# [std_like]=( argv=( "-n" "+5" ) ) 293 ) 294 expected_output=$'' 295 ) 296# Note: following test case trips up legacy Solaris 'tail' as well 297# ( 298# name="revlist5pr" 299# input=$'1\n2\n3\n4' 300# compound -A tail_args=( 301# [legacy]=( argv=( "+5r" ) ) 302# [std_like]=( argv=( "-n" "+5" "-r" ) ) 303# ) 304# expected_output=$'' 305# ) 306) 307 308for testid in "${!testcases[@]}" ; do 309 nameref tc=testcases[${testid}] 310 311 for argv_variants in "${!tc.tail_args[@]}" ; do 312 nameref argv=tc.tail_args[${argv_variants}].argv 313 output=$( 314 set -o pipefail 315 (trap "" PIPE ; print -r -- "${tc.input}") | $TAIL "${argv[@]}" 316 ) || err_exit "test ${tc.name}/${argv_variants}: command failed with exit code $?" 317 318 [[ "${output}" == "${tc.expected_output}" ]] || err_exit "test ${tc.name}/${argv_variants}: Expected $(doublebackslashquote "${tc.expected_output}"), got $(doublebackslashquote "${output}")" 319 done 320done 321 322 323# test2: test "tail -r </etc/profile | rev -l" vs. "cat </etc/profile" 324[[ "$($TAIL -r </etc/profile | rev -l)" == "$( cat /etc/profile )" ]] || err_exit "'tail -r </etc/profile | rev -l' output does not match 'cat /etc/profile'" 325 326# Test case not applicable to FreeBSD 'tail' 327# test 3: ast-ksh.2009-05-05 "tail" builtin may crash if we pass unsupported long options 328#$SHELL -o errexit -c 'builtin tail ; print "hello" | tail --attack_of_chicken_monsters' >/dev/null 2>&1 329#(( $? == 2 )) || err_exit "expected exit code 2 for unsupported long option, got $?" 330 331 332# test 4: FIFO tests 333 334# FIFO test functions 335# (we use functions here to do propper garbage collection) 336function test_tail_fifo_1 337{ 338 typeset tail_cmd="$1" 339 integer i 340 integer tail_pid=-1 341 342 # cleanup trap 343 trap "rm -f tailtestfifo tailout" EXIT 344 345 # create test FIFO 346 mkfifo tailtestfifo 347 348 ${tail_cmd} -f <tailtestfifo >tailout & 349 tail_pid=$! 350 351 myintseq 20 >tailtestfifo 352 353 waitpidtimeout ${tail_pid} 5 354 355 if isvalidpid ${tail_pid} ; then 356 err_exit "test_tail_fifo_1: # tail hung (not expected)" 357 kill -KILL ${tail_pid} 358 fi 359 360 wait || err_exit "tail child returned non-zero exit code=$?" 361 362 [[ "$(cat tailout)" == $'11\n12\n13\n14\n15\n16\n17\n18\n19\n20' ]] || err_exit "test_tail_fifo_1: Expected $(doublebackslashquote '11\n12\n13\n14\n15\n16\n17\n18\n19\n20'), got $(doublebackslashquote "$(cat tailout)")" 363 364 return 0 365} 366 367function test_tail_fifo_2 368{ 369 typeset tail_cmd="$1" 370 integer i 371 integer tail_pid=-1 372 373 # cleanup trap 374 trap "rm -f tailtestfifo tailout" EXIT 375 376 # create test FIFO 377 mkfifo tailtestfifo 378 379 ${tail_cmd} -f tailtestfifo >tailout & 380 tail_pid=$! 381 382 myintseq 14 >tailtestfifo 383 384 waitpidtimeout ${tail_pid} 5 385 386 if isvalidpid ${tail_pid} ; then 387 [[ "$(cat tailout)" == $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14' ]] || err_exit "test_tail_fifo_2: Expected $(doublebackslashquote $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14'), got $(doublebackslashquote "$(cat tailout)")" 388 389 myintseq 15 >>tailtestfifo 390 391 waitpidtimeout ${tail_pid} 5 392 393 if isvalidpid ${tail_pid} ; then 394 kill -KILL ${tail_pid} 395 else 396 err_exit "test_tail_fifo_2: # tail exit with return code $? (not expected)" 397 fi 398 fi 399 400 wait || err_exit "tail child returned non-zero exit code=$?" 401 402 [[ "$(cat tailout)" == $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15' ]] || err_exit "test_tail_fifo_2: Expected $(doublebackslashquote $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15'), got $(doublebackslashquote "$(cat tailout)")" 403 404 return 0 405} 406 407# fixme: This should test /usr/bin/tail and /usr/xpg4/bin/tail in Solaris 408test_tail_fifo_1 "$TAIL" 409test_tail_fifo_2 "$TAIL" 410 411 412# test 5: "tail -f" tests 413function followtest1 414{ 415 typeset -r FOLLOWFILE="followfile.txt" 416 typeset -r OUTFILE="outfile.txt" 417 418 typeset title="$1" 419 typeset testcmd="$2" 420 typeset usenewline=$3 421 typeset followstr="" 422 typeset newline="" 423 integer i 424 integer tailchild=-1 425 426 if ${usenewline} ; then 427 newline=$'\n' 428 fi 429 430 rm -f "${FOLLOWFILE}" "${OUTFILE}" 431 print -n "${newline}" > "${FOLLOWFILE}" 432 433 ${testcmd} -f "${FOLLOWFILE}" >"${OUTFILE}" & 434 (( tailchild=$! )) 435 436 for (( i=0 ; i < 10 ; i++)) ; do 437 followstr+="${newline}${i}" 438 print -n "${i}${newline}" >>"${FOLLOWFILE}" 439 sleep 2 440 441 [[ "$( < "${OUTFILE}")" == "${followstr}" ]] || err_exit "${title}: Expected $(doublebackslashquote "${followstr}"), got "$(doublebackslashquote "$( < "${OUTFILE}")")"" 442 done 443 444 kill -KILL ${tailchild} 2>/dev/null 445 #kill -TERM ${tailchild} 2>/dev/null 446 waitpidtimeout ${tailchild} 5 447 448 if isvalidpid ${tailchild} ; then 449 err_exit "${title}: tail pid=${tailchild} hung." 450 kill -KILL ${tailchild} 2>/dev/null 451 fi 452 453 wait ${tailchild} 2>/dev/null 454 455 rm -f "${FOLLOWFILE}" "${OUTFILE}" 456 457 return 0 458} 459 460followtest1 "test5a" "$TAIL" true 461# fixme: later we should test this, too: 462#followtest1 "test5b" "tail" false 463#followtest1 "test5c" "/usr/xpg4/bin/tail" true 464#followtest1 "test5d" "/usr/xpg4/bin/tail" false 465#followtest1 "test5e" "/usr/bin/tail" true 466#followtest1 "test5f" "/usr/bin/tail" false 467 468 469# test 6: "tail -f" tests 470function followtest2 471{ 472 typeset -r FOLLOWFILE="followfile.txt" 473 typeset -r OUTFILE="outfile.txt" 474 475 typeset title="$1" 476 typeset testcmd="$2" 477 integer tailchild=-1 478 479 rm -f "${FOLLOWFILE}" "${OUTFILE}" 480 481 myintseq 50000 >"${FOLLOWFILE}" 482 483 ${testcmd} -n 60000 -f "${FOLLOWFILE}" >"${OUTFILE}" & 484 (( tailchild=$! )) 485 486 sleep 10 487 488 kill -KILL ${tailchild} 2>/dev/null 489 #kill -TERM ${tailchild} 2>/dev/null 490 waitpidtimeout ${tailchild} 5 491 492 if isvalidpid ${tailchild} ; then 493 err_exit "${title}: tail pid=${tailchild} hung." 494 kill -KILL ${tailchild} 2>/dev/null 495 fi 496 497 wait ${tailchild} 2>/dev/null 498 499 # this tail should be an external process 500 outstr=$(/usr/bin/tail "${OUTFILE}") || err_exit "tail returned non-zero exit code $?" 501 [[ "${outstr}" == 49991*50000 ]] || err_exit "${title}: Expected match for 49991*50000, got "$(singlebackslashquote "${outstr}")"" 502 503 rm -f "${FOLLOWFILE}" "${OUTFILE}" 504 505 return 0 506} 507 508followtest2 "test6a" "$TAIL" 509followtest2 "test6b" "$TAIL" 510# fixme: later we should test this, too: 511#followtest2 "test6c" "/usr/bin/tail" 512 513 514# cleanup 515cd "${ocwd}" 516rmdir "${tmpdir}" || err_exit "Cannot remove temporary directory ${tmpdir}". 517 518 519# tests done 520exit $((Errors)) 521