xref: /illumos-gate/usr/src/cmd/tail/tests/sun_solaris_tail.sh (revision 4c28a617e3922d92a58e813a5b955eb526b9c386)
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