# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. # # # Test whether the ksh93/libcmd tail builtin is compatible to # Solaris/SystemV { /usr/bin/tail, /usr/xpg4/bin/tail } and # POSIX "tail" # # test setup function err_exit { print -u2 -n "\t" print -u2 -r ${Command}[$1]: "${@:2}" (( Errors < 127 && Errors++ )) } alias err_exit='err_exit $LINENO' set -o nounset Command=${0##*/} integer Errors=0 # common functions function isvalidpid { kill -0 ${1} 2>/dev/null && return 0 return 1 } function waitpidtimeout { integer pid=$1 float timeout=$2 float i float -r STEP=0.5 # const (( timeout=timeout/STEP )) for (( i=0 ; i < timeout ; i+=STEP )) ; do isvalidpid ${pid} || break sleep ${STEP} done return 0 } function myintseq { integer i case $# in 1) for (( i=1 ; i <= $1 ; i++ )) ; do printf "%d\n" i done ;; 2) for (( i=$1 ; i <= $2 ; i++ )) ; do printf "%d\n" i done ;; 3) for (( i=$1 ; i <= $3 ; i+=$2 )) ; do printf "%d\n" i done ;; *) print -u2 -f "%s: Illegal number of arguments %d\n" "$0" $# return 1 ;; esac return 0 } # quote input string but use single-backslash that "err_exit" prints # the strings correctly function singlebackslashquote { typeset s s="$(printf "%q\n" "$1")" print -r "$s" return 0 } # quote input string but use double-backslash that "err_exit" prints # the strings correctly function doublebackslashquote { typeset s s="$(printf "%q\n" "$1")" s="${s//\\/\\\\}" print -r "$s" return 0 } # main builtin mktemp || err_exit "mktemp builtin not found" builtin rm || err_exit "rm builtin not found" builtin tail || err_exit "tail builtin not found" typeset ocwd typeset tmpdir # create temporary test directory ocwd="$PWD" tmpdir="$(mktemp -t -d "test_sun_solaris_builtin_tail.XXXXXXXX")" || err_exit "Cannot create temporary directory" cd "${tmpdir}" || { err_exit "cd ${tmpdir} failed." ; exit $((Errors)) ; } # run tests: # test1: basic tests compound -a testcases=( ( name="reverse_n" input=$'hello\nworld' compound -A tail_args=( [legacy]=( argv=( "-r" ) ) ) expected_output=$'world\nhello' ) ( name="revlist0n" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-0" ) ) [std_like]=( argv=( "-n" "0" ) ) ) expected_output=$'' ) ( name="revlist0nr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-0r" ) ) [std_like]=( argv=( "-n" "0" "-r" ) ) [long_options]=( argv=( "--lines" "0" "--reverse" ) ) ) expected_output=$'' ) ( name="revlist1n" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-1" ) ) [std_like]=( argv=( "-n" "1" ) ) [long_options]=( argv=( "--lines" "1" ) ) ) expected_output=$'4' ) ( name="revlist1nr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-1r" ) ) [std_like]=( argv=( "-n" "1" "-r" ) ) [long_options]=( argv=( "--lines" "1" "--reverse" ) ) ) expected_output=$'4' ) ( name="revlist2n" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-2" ) ) [std_like]=( argv=( "-n" "2" ) ) ) expected_output=$'3\n4' ) ( name="revlist2nr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-2r" ) ) [std_like]=( argv=( "-n" "2" "-r" ) ) ) expected_output=$'4\n3' ) ( name="revlist3nr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-3r" ) ) [std_like]=( argv=( "-n" "3" "-r" ) ) ) expected_output=$'4\n3\n2' ) ( name="revlist2p" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+2" ) ) [std_like]=( argv=( "-n" "+2" ) ) ) expected_output=$'2\n3\n4' ) ( name="revlist2pr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+2r" ) ) [std_like]=( argv=( "-n" "+2" "-r" ) ) ) expected_output=$'4\n3\n2' ) ( name="revlist3p" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+3" ) ) [std_like]=( argv=( "-n" "+3" ) ) ) expected_output=$'3\n4' ) ( name="revlist3pr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+3r" ) ) [std_like]=( argv=( "-n" "+3" "-r" ) ) ) expected_output=$'4\n3' ) ( name="revlist4p" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+4" ) ) [std_like]=( argv=( "-n" "+4" ) ) ) expected_output=$'4' ) ( name="revlist4pr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+4r" ) ) [std_like]=( argv=( "-n" "+4" "-r" ) ) ) expected_output=$'4' ) ( name="revlist5p" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+5" ) ) [std_like]=( argv=( "-n" "+5" ) ) ) expected_output=$'' ) ( name="revlist5pr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+5r" ) ) [std_like]=( argv=( "-n" "+5" "-r" ) ) ) expected_output=$'' ) ) for testid in "${!testcases[@]}" ; do nameref tc=testcases[${testid}] for argv_variants in "${!tc.tail_args[@]}" ; do nameref argv=tc.tail_args[${argv_variants}].argv output=$( set -o pipefail (trap "" PIPE ; print -r -- "${tc.input}") | tail "${argv[@]}" ) || err_exit "test ${tc.name}/${argv_variants}: command failed with exit code $?" [[ "${output}" == "${tc.expected_output}" ]] || err_exit "test ${tc.name}/${argv_variants}: Expected $(doublebackslashquote "${tc.expected_output}"), got $(doublebackslashquote "${output}")" done done # test2: test "tail -r /dev/null 2>&1 (( $? == 2 )) || err_exit "expected exit code 2 for unsupported long option, got $?" # test 4: FIFO tests # FIFO test functions # (we use functions here to do propper garbage collection) function test_tail_fifo_1 { typeset tail_cmd="$1" integer i integer tail_pid=-1 # cleanup trap trap "rm -f tailtestfifo tailout" EXIT # create test FIFO mkfifo tailtestfifo ${tail_cmd} -f tailout & tail_pid=$! myintseq 20 >tailtestfifo waitpidtimeout ${tail_pid} 5 if isvalidpid ${tail_pid} ; then err_exit "test_tail_fifo_1: # tail hung (not expected)" kill -KILL ${tail_pid} fi wait || err_exit "tail child returned non-zero exit code=$?" [[ "$(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)")" return 0 } function test_tail_fifo_2 { typeset tail_cmd="$1" integer i integer tail_pid=-1 # cleanup trap trap "rm -f tailtestfifo tailout" EXIT # create test FIFO mkfifo tailtestfifo ${tail_cmd} -f tailtestfifo >tailout & tail_pid=$! myintseq 14 >tailtestfifo waitpidtimeout ${tail_pid} 5 if isvalidpid ${tail_pid} ; then [[ "$(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)")" myintseq 15 >>tailtestfifo waitpidtimeout ${tail_pid} 5 if isvalidpid ${tail_pid} ; then kill -KILL ${tail_pid} else err_exit "test_tail_fifo_2: # tail exit with return code $? (not expected)" fi fi wait || err_exit "tail child returned non-zero exit code=$?" [[ "$(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)")" return 0 } # fixme: This should test /usr/bin/tail and /usr/xpg4/bin/tail in Solaris test_tail_fifo_1 "tail" test_tail_fifo_2 "tail" # test 5: "tail -f" tests function followtest1 { typeset -r FOLLOWFILE="followfile.txt" typeset -r OUTFILE="outfile.txt" typeset title="$1" typeset testcmd="$2" typeset usenewline=$3 typeset followstr="" typeset newline="" integer i integer tailchild=-1 if ${usenewline} ; then newline=$'\n' fi rm -f "${FOLLOWFILE}" "${OUTFILE}" print -n "${newline}" > "${FOLLOWFILE}" ${testcmd} -f "${FOLLOWFILE}" >"${OUTFILE}" & (( tailchild=$! )) for (( i=0 ; i < 10 ; i++)) ; do followstr+="${newline}${i}" print -n "${i}${newline}" >>"${FOLLOWFILE}" sleep 2 [[ "$( < "${OUTFILE}")" == "${followstr}" ]] || err_exit "${title}: Expected $(doublebackslashquote "${followstr}"), got "$(doublebackslashquote "$( < "${OUTFILE}")")"" done kill -KILL ${tailchild} 2>/dev/null #kill -TERM ${tailchild} 2>/dev/null waitpidtimeout ${tailchild} 5 if isvalidpid ${tailchild} ; then err_exit "${title}: tail pid=${tailchild} hung." kill -KILL ${tailchild} 2>/dev/null fi wait ${tailchild} 2>/dev/null rm -f "${FOLLOWFILE}" "${OUTFILE}" return 0 } followtest1 "test5a" "tail" true # fixme: later we should test this, too: #followtest1 "test5b" "tail" false #followtest1 "test5c" "/usr/xpg4/bin/tail" true #followtest1 "test5d" "/usr/xpg4/bin/tail" false #followtest1 "test5e" "/usr/bin/tail" true #followtest1 "test5f" "/usr/bin/tail" false # test 6: "tail -f" tests function followtest2 { typeset -r FOLLOWFILE="followfile.txt" typeset -r OUTFILE="outfile.txt" typeset title="$1" typeset testcmd="$2" integer tailchild=-1 rm -f "${FOLLOWFILE}" "${OUTFILE}" myintseq 50000 >"${FOLLOWFILE}" ${testcmd} -n 60000 -f "${FOLLOWFILE}" >"${OUTFILE}" & (( tailchild=$! )) sleep 10 kill -KILL ${tailchild} 2>/dev/null #kill -TERM ${tailchild} 2>/dev/null waitpidtimeout ${tailchild} 5 if isvalidpid ${tailchild} ; then err_exit "${title}: tail pid=${tailchild} hung." kill -KILL ${tailchild} 2>/dev/null fi wait ${tailchild} 2>/dev/null # this tail should be an external process outstr=$(/usr/bin/tail "${OUTFILE}") || err_exit "tail returned non-zero exit code $?" [[ "${outstr}" == 49991*50000 ]] || err_exit "${title}: Expected match for 49991*50000, got "$(singlebackslashquote "${outstr}")"" rm -f "${FOLLOWFILE}" "${OUTFILE}" return 0 } followtest2 "test6a" "tail" followtest2 "test6b" "/usr/xpg4/bin/tail" # fixme: later we should test this, too: #followtest2 "test6c" "/usr/bin/tail" # cleanup cd "${ocwd}" rmdir "${tmpdir}" || err_exit "Cannot remove temporary directory ${tmpdir}". # tests done exit $((Errors))