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