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