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