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 2007 Sun Microsystems, Inc.  All rights reserved.
24# Use is subject to license terms.
25#
26# Copyright (c) 2012, 2020 by Delphix. All rights reserved.
27#
28
29. ${STF_TOOLS}/include/stf.shlib
30
31# Output an assertion
32#
33# $@ - assertion text
34
35function log_assert
36{
37	_printline ASSERTION: "$@"
38}
39
40# Output a comment
41#
42# $@ - comment text
43
44function log_note
45{
46	_printline NOTE: "$@"
47}
48
49# Execute and print command with status where success equals non-zero result
50#
51# $@ - command to execute
52#
53# return 0 if command fails, otherwise return 1
54
55function log_neg
56{
57	log_neg_expect ""  "$@"
58	return $?
59}
60
61# Execute a positive test and exit $STF_FAIL is test fails
62#
63# $@ - command to execute
64
65function log_must
66{
67	log_pos "$@"
68	(( $? != 0 )) && log_fail
69}
70
71# Execute a positive test but retry the command on failure if the output
72# matches an expected pattern.  Otherwise behave like log_must and exit
73# $STF_FAIL is test fails.
74#
75# $1 - retry keyword
76# $2 - retry attempts
77# $3-$@ - command to execute
78#
79function log_must_retry
80{
81	typeset out=""
82	typeset logfile="/tmp/log.$$"
83	typeset status=1
84	typeset expect=$1
85	typeset retry=$2
86	typeset delay=1
87	shift 2
88
89	while [[ -e $logfile ]]; do
90		logfile="$logfile.$$"
91	done
92
93	while (( $retry > 0 )); do
94		"$@" 2>$logfile
95		status=$?
96		out="cat $logfile"
97
98		if (( $status == 0 )); then
99			$out | egrep -i "internal error|assertion failed" \
100				> /dev/null 2>&1
101			# internal error or assertion failed
102			if [[ $? -eq 0 ]]; then
103				print -u2 $($out)
104				_printerror "$@" "internal error or" \
105					" assertion failure exited $status"
106				status=1
107			else
108				[[ -n $LOGAPI_DEBUG ]] && print $($out)
109				_printsuccess "$@"
110			fi
111			break
112		else
113			$out | grep -i "$expect" > /dev/null 2>&1
114			if (( $? == 0 )); then
115				print -u2 $($out)
116				_printerror "$@" "Retry in $delay seconds"
117				sleep $delay
118
119				(( retry=retry - 1 ))
120				(( delay=delay * 2 ))
121			else
122				break;
123			fi
124		fi
125	done
126
127	if (( $status != 0 )) ; then
128		print -u2 $($out)
129		_printerror "$@" "exited $status"
130	fi
131
132	_recursive_output $logfile "false"
133	return $status
134}
135
136# Execute a positive test and exit $STF_FAIL is test fails after being
137# retried up to 5 times when the command returns the keyword "busy".
138#
139# $@ - command to execute
140function log_must_busy
141{
142	log_must_retry "busy" 5 "$@"
143	(( $? != 0 )) && log_fail
144}
145
146# Execute a negative test and exit $STF_FAIL if test passes
147#
148# $@ - command to execute
149
150function log_mustnot
151{
152	log_neg "$@"
153	(( $? != 0 )) && log_fail
154}
155
156# Execute a negative test with keyword expected, and exit
157# $STF_FAIL if test passes
158#
159# $1 - keyword expected
160# $2-$@ - command to execute
161
162function log_mustnot_expect
163{
164	log_neg_expect "$@"
165	(( $? != 0 )) && log_fail
166}
167
168# Signal numbers are platform-dependent
169case $(uname) in
170Darwin|FreeBSD)
171	SIGBUS=10
172	SIGSEGV=11
173	;;
174illumos|Linux|*)
175	SIGBUS=7
176	SIGSEGV=11
177	;;
178esac
179EXIT_SUCCESS=0
180EXIT_NOTFOUND=127
181EXIT_SIGNAL=256
182EXIT_SIGBUS=$((EXIT_SIGNAL + SIGBUS))
183EXIT_SIGSEGV=$((EXIT_SIGNAL + SIGSEGV))
184
185# Execute and print command with status where success equals non-zero result
186# or output includes expected keyword
187#
188# $1 - keyword expected
189# $2-$@ - command to execute
190#
191# return 0 if command fails, or the output contains the keyword expected,
192# return 1 otherwise
193
194function log_neg_expect
195{
196	typeset out=""
197	typeset logfile="/tmp/log.$$"
198	typeset ret=1
199	typeset expect=$1
200	shift
201
202	while [[ -e $logfile ]]; do
203		logfile="$logfile.$$"
204	done
205
206	"$@" 2>$logfile
207	typeset status=$?
208	out="cat $logfile"
209
210	# unexpected status
211	if (( $status == EXIT_SUCCESS )); then
212		 print -u2 $($out)
213		_printerror "$@" "unexpectedly exited $status"
214	# missing binary
215	elif (( $status == EXIT_NOTFOUND )); then
216		print -u2 $($out)
217		_printerror "$@" "unexpectedly exited $status (File not found)"
218	# bus error - core dump
219	elif (( $status == EXIT_SIGBUS )); then
220		print -u2 $($out)
221		_printerror "$@" "unexpectedly exited $status (Bus Error)"
222	# segmentation violation - core dump
223	elif (( $status == EXIT_SIGSEGV )); then
224		print -u2 $($out)
225		_printerror "$@" "unexpectedly exited $status (SEGV)"
226	else
227		$out | egrep -i "internal error|assertion failed" \
228			> /dev/null 2>&1
229		# internal error or assertion failed
230		if (( $? == 0 )); then
231			print -u2 $($out)
232			_printerror "$@" "internal error or assertion failure" \
233				" exited $status"
234		elif [[ -n $expect ]] ; then
235			$out | grep -i "$expect" > /dev/null 2>&1
236			if (( $? == 0 )); then
237				ret=0
238			else
239				print -u2 $($out)
240				_printerror "$@" "unexpectedly exited $status"
241			fi
242		else
243			ret=0
244		fi
245
246		if (( $ret == 0 )); then
247			[[ -n $LOGAPI_DEBUG ]] && print $($out)
248			_printsuccess "$@" "exited $status"
249		fi
250	fi
251	_recursive_output $logfile "false"
252	return $ret
253}
254
255# Execute and print command with status where success equals zero result
256#
257# $@ command to execute
258#
259# return command exit status
260
261function log_pos
262{
263	typeset out=""
264	typeset logfile="/tmp/log.$$"
265
266	while [[ -e $logfile ]]; do
267		logfile="$logfile.$$"
268	done
269
270	"$@" 2>$logfile
271	typeset status=$?
272	out="cat $logfile"
273
274	if (( $status != 0 )) ; then
275		print -u2 $($out)
276		_printerror "$@" "exited $status"
277	else
278		$out | egrep -i "internal error|assertion failed" \
279			> /dev/null 2>&1
280		# internal error or assertion failed
281		if [[ $? -eq 0 ]]; then
282			print -u2 $($out)
283			_printerror "$@" "internal error or assertion failure" \
284				" exited $status"
285			status=1
286		else
287			[[ -n $LOGAPI_DEBUG ]] && print $($out)
288			_printsuccess "$@"
289		fi
290	fi
291	_recursive_output $logfile "false"
292	return $status
293}
294
295# Set an exit handler
296#
297# $@ - function(s) to perform on exit
298
299function log_onexit
300{
301	_CLEANUP=("$*")
302}
303
304# Push an exit handler on the cleanup stack
305#
306# $@ - function(s) to perform on exit
307
308function log_onexit_push
309{
310	_CLEANUP+=("$*")
311}
312
313# Pop an exit handler off the cleanup stack
314
315function log_onexit_pop
316{
317	_CLEANUP=("${_CLEANUP[@]:0:${#_CLEANUP[@]}-1}")
318}
319
320#
321# Exit functions
322#
323
324# Perform cleanup and exit $STF_PASS
325#
326# $@ - message text
327
328function log_pass
329{
330	_endlog $STF_PASS "$@"
331}
332
333# Perform cleanup and exit $STF_FAIL
334#
335# $@ - message text
336
337function log_fail
338{
339	_endlog $STF_FAIL "$@"
340}
341
342# Perform cleanup and exit $STF_UNRESOLVED
343#
344# $@ - message text
345
346function log_unresolved
347{
348	_endlog $STF_UNRESOLVED "$@"
349}
350
351# Perform cleanup and exit $STF_NOTINUSE
352#
353# $@ - message text
354
355function log_notinuse
356{
357	_endlog $STF_NOTINUSE "$@"
358}
359
360# Perform cleanup and exit $STF_UNSUPPORTED
361#
362# $@ - message text
363
364function log_unsupported
365{
366	_endlog $STF_UNSUPPORTED "$@"
367}
368
369# Perform cleanup and exit $STF_UNTESTED
370#
371# $@ - message text
372
373function log_untested
374{
375	_endlog $STF_UNTESTED "$@"
376}
377
378# Perform cleanup and exit $STF_UNINITIATED
379#
380# $@ - message text
381
382function log_uninitiated
383{
384	_endlog $STF_UNINITIATED "$@"
385}
386
387# Perform cleanup and exit $STF_NORESULT
388#
389# $@ - message text
390
391function log_noresult
392{
393	_endlog $STF_NORESULT "$@"
394}
395
396# Perform cleanup and exit $STF_WARNING
397#
398# $@ - message text
399
400function log_warning
401{
402	_endlog $STF_WARNING "$@"
403}
404
405# Perform cleanup and exit $STF_TIMED_OUT
406#
407# $@ - message text
408
409function log_timed_out
410{
411	_endlog $STF_TIMED_OUT "$@"
412}
413
414# Perform cleanup and exit $STF_OTHER
415#
416# $@ - message text
417
418function log_other
419{
420	_endlog $STF_OTHER "$@"
421}
422
423function set_main_pid
424{
425	_MAINPID=$1
426}
427
428#
429# Internal functions
430#
431
432# Execute custom callback scripts on test failure
433#
434# callback script paths are stored in TESTFAIL_CALLBACKS, delimited by ':'.
435
436function _execute_testfail_callbacks
437{
438	typeset callback
439
440	print "$TESTFAIL_CALLBACKS:" | while read -d ":" callback; do
441		if [[ -n "$callback" ]] ; then
442			log_note "Performing test-fail callback ($callback)"
443			$callback
444		fi
445	done
446}
447
448# Perform cleanup and exit
449#
450# $1 - stf exit code
451# $2-$n - message text
452
453function _endlog
454{
455	typeset logfile="/tmp/log.$$"
456	_recursive_output $logfile
457
458	typeset exitcode=$1
459	shift
460	(( ${#@} > 0 )) && _printline "$@"
461
462	#
463	# If we're running in a subshell then just exit and let
464	# the parent handle the failures
465	#
466	if [[ -n "$_MAINPID" && $$ != "$_MAINPID" ]]; then
467		log_note "subshell exited: "$_MAINPID
468		exit $exitcode
469	fi
470
471	if [[ $exitcode == $STF_FAIL ]] ; then
472		_execute_testfail_callbacks
473	fi
474
475	typeset stack=("${_CLEANUP[@]}")
476	log_onexit ""
477	typeset i=${#stack[@]}
478	while (( i-- )); do
479		typeset cleanup="${stack[i]}"
480		log_note "Performing local cleanup via log_onexit ($cleanup)"
481		$cleanup
482	done
483
484	exit $exitcode
485}
486
487# Output a formatted line
488#
489# $@ - message text
490
491function _printline
492{
493	print "$@"
494}
495
496# Output an error message
497#
498# $@ - message text
499
500function _printerror
501{
502	_printline ERROR: "$@"
503}
504
505# Output a success message
506#
507# $@ - message text
508
509function _printsuccess
510{
511	_printline SUCCESS: "$@"
512}
513
514# Output logfiles recursively
515#
516# $1 - start file
517# $2 - indicate whether output the start file itself, default as yes.
518
519function _recursive_output #logfile
520{
521	typeset logfile=$1
522
523	while [[ -e $logfile ]]; do
524		if [[ -z $2 || $logfile != $1 ]]; then
525			cat $logfile
526		fi
527		rm -f $logfile
528		logfile="$logfile.$$"
529        done
530}
531