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