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