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