github.com/containers/podman/v5@v5.1.0-rc1/test/system/helpers.bash (about) 1 # -*- bash -*- 2 3 # Podman command to run; may be podman-remote 4 PODMAN=${PODMAN:-podman} 5 QUADLET=${QUADLET:-/usr/libexec/podman/quadlet} 6 7 # crun or runc, unlikely to change. Cache, because it's expensive to determine. 8 PODMAN_RUNTIME= 9 10 # Standard image to use for most tests 11 PODMAN_TEST_IMAGE_REGISTRY=${PODMAN_TEST_IMAGE_REGISTRY:-"quay.io"} 12 PODMAN_TEST_IMAGE_USER=${PODMAN_TEST_IMAGE_USER:-"libpod"} 13 PODMAN_TEST_IMAGE_NAME=${PODMAN_TEST_IMAGE_NAME:-"testimage"} 14 PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"20240123"} 15 PODMAN_TEST_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG" 16 17 # Larger image containing systemd tools. 18 PODMAN_SYSTEMD_IMAGE_NAME=${PODMAN_SYSTEMD_IMAGE_NAME:-"systemd-image"} 19 PODMAN_SYSTEMD_IMAGE_TAG=${PODMAN_SYSTEMD_IMAGE_TAG:-"20240124"} 20 PODMAN_SYSTEMD_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_SYSTEMD_IMAGE_NAME:$PODMAN_SYSTEMD_IMAGE_TAG" 21 22 # Remote image that we *DO NOT* fetch or keep by default; used for testing pull 23 # This has changed in 2021, from 0 through 3, various iterations of getting 24 # multiarch to work. It should change only very rarely. 25 PODMAN_NONLOCAL_IMAGE_TAG=${PODMAN_NONLOCAL_IMAGE_TAG:-"00000004"} 26 PODMAN_NONLOCAL_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_NONLOCAL_IMAGE_TAG" 27 28 # Because who wants to spell that out each time? 29 IMAGE=$PODMAN_TEST_IMAGE_FQN 30 SYSTEMD_IMAGE=$PODMAN_SYSTEMD_IMAGE_FQN 31 32 # Default timeout for a podman command. 33 PODMAN_TIMEOUT=${PODMAN_TIMEOUT:-120} 34 35 # Prompt to display when logging podman commands; distinguish root/rootless 36 _LOG_PROMPT='$' 37 if [ $(id -u) -eq 0 ]; then 38 _LOG_PROMPT='#' 39 fi 40 41 ############################################################################### 42 # BEGIN tools for fetching & caching test images 43 # 44 # Registries are flaky: any time we have to pull an image, that's a risk. 45 # 46 47 # Store in a semipermanent location. Not important for CI, but nice for 48 # developers so test restarts don't hang fetching images. 49 export PODMAN_IMAGECACHE=${BATS_TMPDIR:-/tmp}/podman-systest-imagecache-$(id -u) 50 mkdir -p ${PODMAN_IMAGECACHE} 51 52 function _prefetch() { 53 local want=$1 54 55 # Do we already have it in image store? 56 run_podman '?' image exists "$want" 57 if [[ $status -eq 0 ]]; then 58 return 59 fi 60 61 # No image. Do we have it already cached? (Replace / and : with --) 62 local cachename=$(sed -e 's;[/:];--;g' <<<"$want") 63 local cachepath="${PODMAN_IMAGECACHE}/${cachename}.tar" 64 if [[ ! -e "$cachepath" ]]; then 65 # Not cached. Fetch it and cache it. Retry twice, because of flakes. 66 cmd="skopeo copy --preserve-digests docker://$want oci-archive:$cachepath" 67 echo "$_LOG_PROMPT $cmd" 68 run $cmd 69 echo "$output" 70 if [[ $status -ne 0 ]]; then 71 echo "# 'pull $want' failed, will retry..." >&3 72 sleep 5 73 74 run $cmd 75 echo "$output" 76 if [[ $status -ne 0 ]]; then 77 echo "# 'pull $want' failed again, will retry one last time..." >&3 78 sleep 30 79 $cmd 80 fi 81 fi 82 fi 83 84 # Kludge alert. 85 # Skopeo has no --storage-driver, --root, or --runroot flags; those 86 # need to be expressed in the destination string inside [brackets]. 87 # See containers-transports(5). So if we see those options in 88 # _PODMAN_TEST_OPTS, transmogrify $want into skopeo form. 89 skopeo_opts='' 90 driver="$(expr "$_PODMAN_TEST_OPTS" : ".*--storage-driver \([^ ]\+\)" || true)" 91 if [[ -n "$driver" ]]; then 92 skopeo_opts+="$driver@" 93 fi 94 95 altroot="$(expr "$_PODMAN_TEST_OPTS" : ".*--root \([^ ]\+\)" || true)" 96 if [[ -n "$altroot" ]] && [[ -d "$altroot" ]]; then 97 skopeo_opts+="$altroot" 98 99 altrunroot="$(expr "$_PODMAN_TEST_OPTS" : ".*--runroot \([^ ]\+\)" || true)" 100 if [[ -n "$altrunroot" ]] && [[ -d "$altrunroot" ]]; then 101 skopeo_opts+="+$altrunroot" 102 fi 103 fi 104 105 if [[ -n "$skopeo_opts" ]]; then 106 want="[$skopeo_opts]$want" 107 fi 108 109 # Cached image is now guaranteed to exist. Be sure to load it 110 # with skopeo, not podman, in order to preserve metadata 111 cmd="skopeo copy --all oci-archive:$cachepath containers-storage:$want" 112 echo "$_LOG_PROMPT $cmd" 113 $cmd 114 } 115 116 117 # Wrapper for skopeo, because skopeo doesn't work rootless if $XDG is unset 118 # (as it is in RHEL gating): it defaults to /run/containers/<uid>, which 119 # of course is a root-only dir, hence fails with permission denied. 120 # -- https://github.com/containers/skopeo/issues/823 121 function skopeo() { 122 local xdg=${XDG_RUNTIME_DIR} 123 if [ -z "$xdg" ]; then 124 if is_rootless; then 125 xdg=/run/user/$(id -u) 126 fi 127 fi 128 XDG_RUNTIME_DIR=${xdg} command skopeo "$@" 129 } 130 131 # END tools for fetching & caching test images 132 ############################################################################### 133 # BEGIN setup/teardown tools 134 135 # Provide common setup and teardown functions, but do not name them such! 136 # That way individual tests can override with their own setup/teardown, 137 # while retaining the ability to include these if they so desire. 138 139 # Setup helper: establish a test environment with exactly the images needed 140 function basic_setup() { 141 # Clean up all containers 142 run_podman rm -t 0 --all --force --ignore 143 144 # ...including external (buildah) ones 145 run_podman ps --all --external --format '{{.ID}} {{.Names}}' 146 for line in "${lines[@]}"; do 147 set $line 148 echo "# setup(): removing stray external container $1 ($2)" >&3 149 run_podman '?' rm -f $1 150 if [[ $status -ne 0 ]]; then 151 echo "# [setup] $_LOG_PROMPT podman rm -f $1" >&3 152 for errline in "${lines[@]}"; do 153 echo "# $errline" >&3 154 done 155 fi 156 done 157 158 # Clean up all images except those desired. 159 # 2023-06-26 REMINDER: it is tempting to think that this is clunky, 160 # wouldn't it be safer/cleaner to just 'rmi -a' then '_prefetch $IMAGE'? 161 # Yes, but it's also tremendously slower: 29m for a CI run, to 39m. 162 # Image loads are slow. 163 found_needed_image= 164 run_podman '?' images --all --format '{{.Repository}}:{{.Tag}} {{.ID}}' 165 166 for line in "${lines[@]}"; do 167 set $line 168 if [[ "$1" == "$PODMAN_TEST_IMAGE_FQN" ]]; then 169 if [[ -z "$PODMAN_TEST_IMAGE_ID" ]]; then 170 # This will probably only trigger the 2nd time through setup 171 PODMAN_TEST_IMAGE_ID=$2 172 fi 173 found_needed_image=1 174 elif [[ "$1" == "$PODMAN_SYSTEMD_IMAGE_FQN" ]]; then 175 # This is a big image, don't force unnecessary pulls 176 : 177 else 178 # Always remove image that doesn't match by name 179 echo "# setup(): removing stray image $1" >&3 180 run_podman rmi --force "$1" >/dev/null 2>&1 || true 181 182 # Tagged image will have same IID as our test image; don't rmi it. 183 if [[ $2 != "$PODMAN_TEST_IMAGE_ID" ]]; then 184 echo "# setup(): removing stray image $2" >&3 185 run_podman rmi --force "$2" >/dev/null 2>&1 || true 186 fi 187 fi 188 done 189 190 # Make sure desired image is present 191 if [[ -z "$found_needed_image" ]]; then 192 _prefetch $PODMAN_TEST_IMAGE_FQN 193 fi 194 195 # Temporary subdirectory, in which tests can write whatever they like 196 # and trust that it'll be deleted on cleanup. 197 # (BATS v1.3 and above provide $BATS_TEST_TMPDIR, but we still use 198 # ancient BATS (v1.1) in RHEL gating tests.) 199 PODMAN_TMPDIR=$(mktemp -d --tmpdir=${BATS_TMPDIR:-/tmp} podman_bats.XXXXXX) 200 201 # runtime is not likely to change 202 if [[ -z "$PODMAN_RUNTIME" ]]; then 203 PODMAN_RUNTIME=$(podman_runtime) 204 fi 205 206 # In the unlikely event that a test runs is() before a run_podman() 207 MOST_RECENT_PODMAN_COMMAND= 208 209 # Test filenames must match ###-name.bats; use "[###] " as prefix 210 run expr "$BATS_TEST_FILENAME" : "^.*/\([0-9]\{3\}\)-[^/]\+\.bats\$" 211 BATS_TEST_NAME_PREFIX="[${output}] " 212 213 # By default, assert() and die() cause an immediate test failure. 214 # Under special circumstances (usually long test loops), tests 215 # can call defer-assertion-failures() to continue going, the 216 # idea being that a large number of failures can show patterns. 217 ASSERTION_FAILURES= 218 immediate-assertion-failures 219 } 220 221 # bail-now is how we terminate a test upon assertion failure. 222 # By default, and the vast majority of the time, it just triggers 223 # immediate test termination; but see defer-assertion-failures, below. 224 function bail-now() { 225 # "false" does not apply to "bail now"! It means "nonzero exit", 226 # which BATS interprets as "yes, bail immediately". 227 false 228 } 229 230 # Invoked on teardown: will terminate immediately if there have been 231 # any deferred test failures; otherwise will reset back to immediate 232 # test termination on future assertions. 233 function immediate-assertion-failures() { 234 function bail-now() { 235 false 236 } 237 238 # Any backlog? 239 if [[ -n "$ASSERTION_FAILURES" ]]; then 240 local n=${#ASSERTION_FAILURES} 241 ASSERTION_FAILURES= 242 die "$n test assertions failed. Search for 'FAIL:' above this line." >&2 243 fi 244 } 245 246 # Used in special test circumstances--typically multi-condition loops--to 247 # continue going even on assertion failures. The test will fail afterward, 248 # usually in teardown. This can be useful to show failure patterns. 249 function defer-assertion-failures() { 250 function bail-now() { 251 ASSERTION_FAILURES+="!" 252 } 253 } 254 255 # Basic teardown: remove all pods and containers 256 function basic_teardown() { 257 echo "# [teardown]" >&2 258 local actions=( 259 "pod rm -t 0 --all --force --ignore" 260 "rm -t 0 --all --force --ignore" 261 "network prune --force" 262 "volume rm -a -f" 263 ) 264 for action in "${actions[@]}"; do 265 run_podman '?' $action 266 267 # The -f commands should never exit nonzero, but if they do we want 268 # to know about it. 269 # FIXME: someday: also test for [[ -n "$output" ]] - can't do this 270 # yet because too many tests don't clean up their containers 271 if [[ $status -ne 0 ]]; then 272 echo "# [teardown] $_LOG_PROMPT podman $action" >&3 273 for line in "${lines[*]}"; do 274 echo "# $line" >&3 275 done 276 277 # Special case for timeout: check for locks (#18514) 278 if [[ $status -eq 124 ]]; then 279 echo "# [teardown] $_LOG_PROMPT podman system locks" >&3 280 run $PODMAN system locks 281 for line in "${lines[*]}"; do 282 echo "# $line" >&3 283 done 284 fi 285 fi 286 done 287 288 command rm -rf $PODMAN_TMPDIR 289 immediate-assertion-failures 290 } 291 292 293 # Provide the above as default methods. 294 function setup() { 295 basic_setup 296 } 297 298 function teardown() { 299 basic_teardown 300 } 301 302 303 # Helpers useful for tests running rmi 304 function archive_image() { 305 local image=$1 306 307 # FIXME: refactor? 308 archive_basename=$(echo $1 | tr -c a-zA-Z0-9._- _) 309 archive=$BATS_TMPDIR/$archive_basename.tar 310 311 run_podman save -o $archive $image 312 } 313 314 function restore_image() { 315 local image=$1 316 317 archive_basename=$(echo $1 | tr -c a-zA-Z0-9._- _) 318 archive=$BATS_TMPDIR/$archive_basename.tar 319 320 run_podman restore $archive 321 } 322 323 # END setup/teardown tools 324 ############################################################################### 325 # BEGIN podman helpers 326 327 # Displays '[HH:MM:SS.NNNNN]' in command output. logformatter relies on this. 328 function timestamp() { 329 date +'[%T.%N]' 330 } 331 332 ################ 333 # run_podman # Invoke $PODMAN, with timeout, using BATS 'run' 334 ################ 335 # 336 # This is the preferred mechanism for invoking podman: first, it 337 # invokes $PODMAN, which may be 'podman-remote' or '/some/path/podman'. 338 # 339 # Second, we use 'timeout' to abort (with a diagnostic) if something 340 # takes too long; this is preferable to a CI hang. 341 # 342 # Third, we log the command run and its output. This doesn't normally 343 # appear in BATS output, but it will if there's an error. 344 # 345 # Next, we check exit status. Since the normal desired code is 0, 346 # that's the default; but the first argument can override: 347 # 348 # run_podman 125 nonexistent-subcommand 349 # run_podman '?' some-other-command # let our caller check status 350 # 351 # Since we use the BATS 'run' mechanism, $output and $status will be 352 # defined for our caller. 353 # 354 function run_podman() { 355 # Number as first argument = expected exit code; default 0 356 # "0+[we]" = require success, but allow warnings/errors 357 local expected_rc=0 358 local allowed_levels="dit" 359 case "$1" in 360 0\+[we]*) allowed_levels+=$(expr "$1" : "^0+\([we]\+\)"); shift;; 361 [0-9]) expected_rc=$1; shift;; 362 [1-9][0-9]) expected_rc=$1; shift;; 363 [12][0-9][0-9]) expected_rc=$1; shift;; 364 '?') expected_rc= ; shift;; # ignore exit code 365 esac 366 367 # Remember command args, for possible use in later diagnostic messages 368 MOST_RECENT_PODMAN_COMMAND="podman $*" 369 370 # BATS >= 1.5.0 treats 127 as a special case, adding a big nasty warning 371 # at the end of the test run if any command exits thus. Silence it. 372 # https://bats-core.readthedocs.io/en/stable/warnings/BW01.html 373 local silence127= 374 if [[ "$expected_rc" = "127" ]]; then 375 # We could use "-127", but that would cause BATS to fail if the 376 # command exits any other status -- and default BATS failure messages 377 # are much less helpful than the run_podman ones. "!" is more flexible. 378 silence127="!" 379 fi 380 381 # stdout is only emitted upon error; this printf is to help in debugging 382 printf "\n%s %s %s %s\n" "$(timestamp)" "$_LOG_PROMPT" "$PODMAN" "$*" 383 # BATS hangs if a subprocess remains and keeps FD 3 open; this happens 384 # if podman crashes unexpectedly without cleaning up subprocesses. 385 run $silence127 timeout --foreground -v --kill=10 $PODMAN_TIMEOUT $PODMAN $_PODMAN_TEST_OPTS "$@" 3>/dev/null 386 # without "quotes", multiple lines are glommed together into one 387 if [ -n "$output" ]; then 388 echo "$(timestamp) $output" 389 390 # FIXME FIXME FIXME: instrumenting to track down #15488. Please 391 # remove once that's fixed. We include the args because, remember, 392 # bats only shows output on error; it's possible that the first 393 # instance of the metacopy warning happens in a test that doesn't 394 # check output, hence doesn't fail. 395 if [[ "$output" =~ Ignoring.global.metacopy.option ]]; then 396 echo "# YO! metacopy warning triggered by: podman $*" >&3 397 fi 398 fi 399 if [ "$status" -ne 0 ]; then 400 echo -n "$(timestamp) [ rc=$status "; 401 if [ -n "$expected_rc" ]; then 402 if [ "$status" -eq "$expected_rc" ]; then 403 echo -n "(expected) "; 404 else 405 echo -n "(** EXPECTED $expected_rc **) "; 406 fi 407 fi 408 echo "]" 409 fi 410 411 if [ "$status" -eq 124 ]; then 412 if expr "$output" : ".*timeout: sending" >/dev/null; then 413 # It's possible for a subtest to _want_ a timeout 414 if [[ "$expected_rc" != "124" ]]; then 415 echo "*** TIMED OUT ***" 416 false 417 fi 418 fi 419 fi 420 421 if [ -n "$expected_rc" ]; then 422 if [ "$status" -ne "$expected_rc" ]; then 423 die "exit code is $status; expected $expected_rc" 424 fi 425 fi 426 427 # Check for "level=<unexpected>" in output, because a successful command 428 # should never issue unwanted warnings or errors. The "0+w" convention 429 # (see top of function) allows our caller to indicate that warnings are 430 # expected, e.g., "podman stop" without -t0. 431 if [[ $status -eq 0 ]]; then 432 # FIXME: don't do this on Debian or RHEL. runc is way too buggy: 433 # - #11784 - lstat /sys/fs/.../*.scope: ENOENT 434 # - #11785 - cannot toggle freezer: cgroups not configured 435 # As of January 2024 the freezer one seems to be fixed in Debian-runc 436 # but not in RHEL8-runc. The lstat one is closed-wontfix. 437 if [[ $PODMAN_RUNTIME != "runc" ]]; then 438 # FIXME: All kube commands emit unpredictable errors: 439 # "Storage for container <X> has been removed" 440 # "no container with ID <X> found in database" 441 # These are level=error but we still get exit-status 0. 442 # Just skip all kube commands completely 443 if [[ ! "$*" =~ kube ]]; then 444 if [[ "$output" =~ level=[^${allowed_levels}] ]]; then 445 die "Command succeeded, but issued unexpected warnings" 446 fi 447 fi 448 fi 449 fi 450 } 451 452 453 # Wait for certain output from a container, indicating that it's ready. 454 function wait_for_output { 455 local sleep_delay=1 456 local how_long=$PODMAN_TIMEOUT 457 local expect= 458 local cid= 459 460 # Arg processing. A single-digit number is how long to sleep between 461 # iterations; a 2- or 3-digit number is the total time to wait; all 462 # else are, in order, the string to expect and the container name/ID. 463 local i 464 for i in "$@"; do 465 if expr "$i" : '[0-9]\+$' >/dev/null; then 466 if [ $i -le 9 ]; then 467 sleep_delay=$i 468 else 469 how_long=$i 470 fi 471 elif [ -z "$expect" ]; then 472 expect=$i 473 else 474 cid=$i 475 fi 476 done 477 478 [ -n "$cid" ] || die "FATAL: wait_for_output: no container name/ID in '$*'" 479 480 t1=$(expr $SECONDS + $how_long) 481 while [ $SECONDS -lt $t1 ]; do 482 run_podman 0+w logs $cid 483 logs=$output 484 if expr "$logs" : ".*$expect" >/dev/null; then 485 return 486 fi 487 488 # Barf if container is not running 489 run_podman inspect --format '{{.State.Running}}' $cid 490 if [ $output != "true" ]; then 491 run_podman inspect --format '{{.State.ExitCode}}' $cid 492 exitcode=$output 493 494 # One last chance: maybe the container exited just after logs cmd 495 run_podman 0+w logs $cid 496 if expr "$logs" : ".*$expect" >/dev/null; then 497 return 498 fi 499 500 die "Container exited (status: $exitcode) before we saw '$expect': $logs" 501 fi 502 503 sleep $sleep_delay 504 done 505 506 die "timed out waiting for '$expect' from $cid" 507 } 508 509 # Shortcut for the lazy 510 function wait_for_ready { 511 wait_for_output 'READY' "$@" 512 } 513 514 ################### 515 # wait_for_file # Returns once file is available on host 516 ################### 517 function wait_for_file() { 518 local file=$1 # The path to the file 519 local _timeout=${2:-5} # Optional; default 5 seconds 520 521 # Wait 522 while [ $_timeout -gt 0 ]; do 523 test -e $file && return 524 sleep 1 525 _timeout=$(( $_timeout - 1 )) 526 done 527 528 die "Timed out waiting for $file" 529 } 530 531 ########################### 532 # wait_for_file_content # Like wait_for_output, but with files (not ctrs) 533 ########################### 534 function wait_for_file_content() { 535 local file=$1 # The path to the file 536 local content=$2 # What to expect in the file 537 local _timeout=${3:-5} # Optional; default 5 seconds 538 539 while :; do 540 grep -q "$content" "$file" && return 541 542 test $_timeout -gt 0 || die "Timed out waiting for '$content' in $file" 543 544 _timeout=$(( $_timeout - 1 )) 545 sleep 1 546 547 # For debugging. Note that file does not necessarily exist yet. 548 if [[ -e "$file" ]]; then 549 echo "[ wait_for_file_content: retrying wait for '$content' in: ]" 550 sed -e 's/^/[ /' -e 's/$/ ]/' <"$file" 551 else 552 echo "[ wait_for_file_content: $file does not exist (yet) ]" 553 fi 554 done 555 } 556 557 # END podman helpers 558 ############################################################################### 559 # BEGIN miscellaneous tools 560 561 # Shortcuts for common needs: 562 function is_rootless() { 563 [ "$(id -u)" -ne 0 ] 564 } 565 566 function is_remote() { 567 [[ "$PODMAN" =~ -remote ]] 568 } 569 570 function is_cgroupsv1() { 571 # WARNING: This will break if there's ever a cgroups v3 572 ! is_cgroupsv2 573 } 574 575 # True if cgroups v2 are enabled 576 function is_cgroupsv2() { 577 cgroup_type=$(stat -f -c %T /sys/fs/cgroup) 578 test "$cgroup_type" = "cgroup2fs" 579 } 580 581 # True if podman is using netavark 582 function is_netavark() { 583 run_podman info --format '{{.Host.NetworkBackend}}' 584 if [[ "$output" =~ netavark ]]; then 585 return 0 586 fi 587 return 1 588 } 589 590 function is_aarch64() { 591 [ "$(uname -m)" == "aarch64" ] 592 } 593 594 function selinux_enabled() { 595 /usr/sbin/selinuxenabled 2> /dev/null 596 } 597 598 # Returns the OCI runtime *basename* (typically crun or runc). Much as we'd 599 # love to cache this result, we probably shouldn't. 600 function podman_runtime() { 601 # This function is intended to be used as '$(podman_runtime)', i.e. 602 # our caller wants our output. It's unsafe to use run_podman(). 603 runtime=$($PODMAN $_PODMAN_TEST_OPTS info --format '{{ .Host.OCIRuntime.Name }}' 2>/dev/null) 604 basename "${runtime:-[null]}" 605 } 606 607 # Returns the storage driver: 'overlay' or 'vfs' 608 function podman_storage_driver() { 609 run_podman info --format '{{.Store.GraphDriverName}}' >/dev/null 610 # Should there ever be a new driver 611 case "$output" in 612 overlay) ;; 613 vfs) ;; 614 *) die "Unknown storage driver '$output'; if this is a new driver, please review uses of this function in tests." ;; 615 esac 616 echo "$output" 617 } 618 619 # Given a (scratch) directory path, returns a set of command-line options 620 # for running an isolated podman that will not step on system podman. Set: 621 # - rootdir, so we don't clobber real images or storage; 622 # - tmpdir, so we use an isolated DB; and 623 # - runroot, out of an abundance of paranoia 624 function podman_isolation_opts() { 625 local path=${1?podman_isolation_opts: missing PATH arg} 626 627 for opt in root runroot tmpdir;do 628 mkdir -p $path/$opt 629 echo " --$opt $path/$opt" 630 done 631 } 632 633 # rhbz#1895105: rootless journald is unavailable except to users in 634 # certain magic groups; which our testuser account does not belong to 635 # (intentional: that is the RHEL default, so that's the setup we test). 636 function journald_unavailable() { 637 if ! is_rootless; then 638 # root must always have access to journal 639 return 1 640 fi 641 642 run journalctl -n 1 643 if [[ $status -eq 0 ]]; then 644 return 1 645 fi 646 647 if [[ $output =~ permission ]]; then 648 return 0 649 fi 650 651 # This should never happen; if it does, it's likely that a subsequent 652 # test will fail. This output may help track that down. 653 echo "WEIRD: 'journalctl -n 1' failed with a non-permission error:" 654 echo "$output" 655 return 1 656 } 657 658 # Returns the name of the local pause image. 659 function pause_image() { 660 # This function is intended to be used as '$(pause_image)', i.e. 661 # our caller wants our output. run_podman() messes with output because 662 # it emits the command invocation to stdout, hence the redirection. 663 run_podman version --format "{{.Server.Version}}-{{.Server.Built}}" >/dev/null 664 echo "localhost/podman-pause:$output" 665 } 666 667 # Wait for the pod (1st arg) to transition into the state (2nd arg) 668 function _ensure_pod_state() { 669 for i in {0..5}; do 670 run_podman pod inspect $1 --format "{{.State}}" 671 if [[ $output == "$2" ]]; then 672 return 673 fi 674 sleep 0.5 675 done 676 677 die "Timed out waiting for pod $1 to enter state $2" 678 } 679 680 # Wait for the container's (1st arg) running state (2nd arg) 681 function _ensure_container_running() { 682 for i in {0..20}; do 683 run_podman container inspect $1 --format "{{.State.Running}}" 684 if [[ $output == "$2" ]]; then 685 return 686 fi 687 sleep 0.5 688 done 689 690 die "Timed out waiting for container $1 to enter state running=$2" 691 } 692 693 ########################### 694 # _add_label_if_missing # make sure skip messages include rootless/remote 695 ########################### 696 function _add_label_if_missing() { 697 local msg="$1" 698 local want="$2" 699 700 if [ -z "$msg" ]; then 701 echo 702 elif expr "$msg" : ".*$want" &>/dev/null; then 703 echo "$msg" 704 else 705 echo "[$want] $msg" 706 fi 707 } 708 709 ###################### 710 # skip_if_no_ssh # ...with an optional message 711 ###################### 712 function skip_if_no_ssh() { 713 if no_ssh; then 714 local msg=$(_add_label_if_missing "$1" "ssh") 715 skip "${msg:-not applicable with no ssh binary}" 716 fi 717 } 718 719 ###################### 720 # skip_if_rootless # ...with an optional message 721 ###################### 722 function skip_if_rootless() { 723 if is_rootless; then 724 local msg=$(_add_label_if_missing "$1" "rootless") 725 skip "${msg:-not applicable under rootless podman}" 726 fi 727 } 728 729 ###################### 730 # skip_if_not_rootless # ...with an optional message 731 ###################### 732 function skip_if_not_rootless() { 733 if ! is_rootless; then 734 local msg=$(_add_label_if_missing "$1" "rootful") 735 skip "${msg:-not applicable under rootlfull podman}" 736 fi 737 } 738 739 #################### 740 # skip_if_remote # ...with an optional message 741 #################### 742 function skip_if_remote() { 743 if is_remote; then 744 local msg=$(_add_label_if_missing "$1" "remote") 745 skip "${msg:-test does not work with podman-remote}" 746 fi 747 } 748 749 ######################## 750 # skip_if_no_selinux # 751 ######################## 752 function skip_if_no_selinux() { 753 if [ ! -e /usr/sbin/selinuxenabled ]; then 754 skip "selinux not available" 755 elif ! /usr/sbin/selinuxenabled; then 756 skip "selinux disabled" 757 fi 758 } 759 760 ####################### 761 # skip_if_cgroupsv1 # ...with an optional message 762 ####################### 763 function skip_if_cgroupsv1() { 764 if ! is_cgroupsv2; then 765 skip "${1:-test requires cgroupsv2}" 766 fi 767 } 768 769 ####################### 770 # skip_if_cgroupsv2 # ...with an optional message 771 ####################### 772 function skip_if_cgroupsv2() { 773 if is_cgroupsv2; then 774 skip "${1:-test requires cgroupsv1}" 775 fi 776 } 777 778 ###################### 779 # skip_if_rootless_cgroupsv1 # ...with an optional message 780 ###################### 781 function skip_if_rootless_cgroupsv1() { 782 if is_rootless; then 783 if ! is_cgroupsv2; then 784 local msg=$(_add_label_if_missing "$1" "rootless cgroupvs1") 785 skip "${msg:-not supported as rootless under cgroupsv1}" 786 fi 787 fi 788 } 789 790 ################################## 791 # skip_if_journald_unavailable # rhbz#1895105: rootless journald permissions 792 ################################## 793 function skip_if_journald_unavailable { 794 if journald_unavailable; then 795 skip "Cannot use rootless journald on this system" 796 fi 797 } 798 799 function skip_if_aarch64 { 800 if is_aarch64; then 801 skip "${msg:-Cannot run this test on aarch64 systems}" 802 fi 803 } 804 805 ######### 806 # die # Abort with helpful message 807 ######### 808 function die() { 809 # FIXME: handle multi-line output 810 echo "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" >&2 811 echo "#| FAIL: $*" >&2 812 echo "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" >&2 813 bail-now 814 } 815 816 ############ 817 # assert # Compare actual vs expected string; fail if mismatch 818 ############ 819 # 820 # Compares string (default: $output) against the given string argument. 821 # By default we do an exact-match comparison against $output, but there 822 # are two different ways to invoke us, each with an optional description: 823 # 824 # assert "EXPECT" [DESCRIPTION] 825 # assert "RESULT" "OP" "EXPECT" [DESCRIPTION] 826 # 827 # The first form (one or two arguments) does an exact-match comparison 828 # of "$output" against "EXPECT". The second (three or four args) compares 829 # the first parameter against EXPECT, using the given OPerator. If present, 830 # DESCRIPTION will be displayed on test failure. 831 # 832 # Examples: 833 # 834 # assert "this is exactly what we expect" 835 # assert "${lines[0]}" =~ "^abc" "first line begins with abc" 836 # 837 function assert() { 838 local actual_string="$output" 839 local operator='==' 840 local expect_string="$1" 841 local testname="$2" 842 843 case "${#*}" in 844 0) die "Internal error: 'assert' requires one or more arguments" ;; 845 1|2) ;; 846 3|4) actual_string="$1" 847 operator="$2" 848 expect_string="$3" 849 testname="$4" 850 ;; 851 *) die "Internal error: too many arguments to 'assert'" ;; 852 esac 853 854 # Comparisons. 855 # Special case: there is no !~ operator, so fake it via '! x =~ y' 856 local not= 857 local actual_op="$operator" 858 if [[ $operator == '!~' ]]; then 859 not='!' 860 actual_op='=~' 861 fi 862 if [[ $operator == '=' || $operator == '==' ]]; then 863 # Special case: we can't use '=' or '==' inside [[ ... ]] because 864 # the right-hand side is treated as a pattern... and '[xy]' will 865 # not compare literally. There seems to be no way to turn that off. 866 if [ "$actual_string" = "$expect_string" ]; then 867 return 868 fi 869 elif [[ $operator == '!=' ]]; then 870 # Same special case as above 871 if [ "$actual_string" != "$expect_string" ]; then 872 return 873 fi 874 else 875 if eval "[[ $not \$actual_string $actual_op \$expect_string ]]"; then 876 return 877 elif [ $? -gt 1 ]; then 878 die "Internal error: could not process 'actual' $operator 'expect'" 879 fi 880 fi 881 882 # Test has failed. Get a descriptive test name. 883 if [ -z "$testname" ]; then 884 testname="${MOST_RECENT_PODMAN_COMMAND:-[no test name given]}" 885 fi 886 887 # Display optimization: the typical case for 'expect' is an 888 # exact match ('='), but there are also '=~' or '!~' or '-ge' 889 # and the like. Omit the '=' but show the others; and always 890 # align subsequent output lines for ease of comparison. 891 local op='' 892 local ws='' 893 if [ "$operator" != '==' ]; then 894 op="$operator " 895 ws=$(printf "%*s" ${#op} "") 896 fi 897 898 # This is a multi-line message, which may in turn contain multi-line 899 # output, so let's format it ourself to make it more readable. 900 local expect_split 901 mapfile -t expect_split <<<"$expect_string" 902 local actual_split 903 mapfile -t actual_split <<<"$actual_string" 904 905 # bash %q is really nice, except for the way it backslashes spaces 906 local -a expect_split_q 907 for line in "${expect_split[@]}"; do 908 local q=$(printf "%q" "$line" | sed -e 's/\\ / /g') 909 expect_split_q+=("$q") 910 done 911 local -a actual_split_q 912 for line in "${actual_split[@]}"; do 913 local q=$(printf "%q" "$line" | sed -e 's/\\ / /g') 914 actual_split_q+=("$q") 915 done 916 917 printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2 918 printf "#| FAIL: %s\n" "$testname" >&2 919 printf "#| expected: %s%s\n" "$op" "${expect_split_q[0]}" >&2 920 local line 921 for line in "${expect_split_q[@]:1}"; do 922 printf "#| > %s%s\n" "$ws" "$line" >&2 923 done 924 printf "#| actual: %s%s\n" "$ws" "${actual_split_q[0]}" >&2 925 for line in "${actual_split_q[@]:1}"; do 926 printf "#| > %s%s\n" "$ws" "$line" >&2 927 done 928 printf "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" >&2 929 bail-now 930 } 931 932 ######## 933 # is # **DEPRECATED**; see assert() above 934 ######## 935 function is() { 936 local actual="$1" 937 local expect="$2" 938 local testname="${3:-${MOST_RECENT_PODMAN_COMMAND:-[no test name given]}}" 939 940 local is_expr= 941 if [ -z "$expect" ]; then 942 if [ -z "$actual" ]; then 943 # Both strings are empty. 944 return 945 fi 946 expect='[no output]' 947 elif [[ "$actual" = "$expect" ]]; then 948 # Strings are identical. 949 return 950 else 951 # Strings are not identical. Are there wild cards in our expect string? 952 if expr "$expect" : ".*[^\\][\*\[]" >/dev/null; then 953 # There is a '[' or '*' without a preceding backslash. 954 is_expr=' (using expr)' 955 elif [[ "${expect:0:1}" = '[' ]]; then 956 # String starts with '[', e.g. checking seconds like '[345]' 957 is_expr=' (using expr)' 958 fi 959 if [[ -n "$is_expr" ]]; then 960 if expr "$actual" : "$expect" >/dev/null; then 961 return 962 fi 963 fi 964 fi 965 966 # This is a multi-line message, which may in turn contain multi-line 967 # output, so let's format it ourself to make it more readable. 968 local -a actual_split 969 readarray -t actual_split <<<"$actual" 970 printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2 971 printf "#| FAIL: $testname\n" >&2 972 printf "#| expected: '%s'%s\n" "$expect" "$is_expr" >&2 973 printf "#| actual: '%s'\n" "${actual_split[0]}" >&2 974 local line 975 for line in "${actual_split[@]:1}"; do 976 printf "#| > '%s'\n" "$line" >&2 977 done 978 printf "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" >&2 979 bail-now 980 } 981 982 #################### 983 # allow_warnings # check cmd output for warning messages other than these 984 #################### 985 # 986 # HEADS UP: Operates on '$lines' array, so, must be invoked after run_podman 987 # 988 function allow_warnings() { 989 for line in "${lines[@]}"; do 990 if [[ "$line" =~ level=[we] ]]; then 991 local ok= 992 for pattern in "$@"; do 993 if [[ "$line" =~ $pattern ]]; then 994 ok=ok 995 fi 996 done 997 if [[ -z "$ok" ]]; then 998 die "Unexpected warning/error in command results: $line" 999 fi 1000 fi 1001 done 1002 } 1003 1004 ##################### 1005 # require_warning # Require the given message, but disallow any others 1006 ##################### 1007 # Optional 2nd argument is a message to display if warning is missing 1008 function require_warning() { 1009 local expect="$1" 1010 local msg="${2:-Did not find expected warning/error message}" 1011 assert "$output" =~ "$expect" "$msg" 1012 allow_warnings "$expect" 1013 } 1014 1015 ############ 1016 # dprint # conditional debug message 1017 ############ 1018 # 1019 # Set PODMAN_TEST_DEBUG to the name of one or more functions you want to debug 1020 # 1021 # Examples: 1022 # 1023 # $ PODMAN_TEST_DEBUG=parse_table bats . 1024 # $ PODMAN_TEST_DEBUG="test_podman_images test_podman_run" bats . 1025 # 1026 function dprint() { 1027 test -z "$PODMAN_TEST_DEBUG" && return 1028 1029 caller="${FUNCNAME[1]}" 1030 1031 # PODMAN_TEST_DEBUG is a space-separated list of desired functions 1032 # e.g. "parse_table test_podman_images" (or even just "table") 1033 for want in $PODMAN_TEST_DEBUG; do 1034 # Check if our calling function matches any of the desired strings 1035 if expr "$caller" : ".*$want" >/dev/null; then 1036 echo "# ${FUNCNAME[1]}() : $*" >&3 1037 return 1038 fi 1039 done 1040 } 1041 1042 1043 ################# 1044 # parse_table # Split a table on '|' delimiters; return space-separated 1045 ################# 1046 # 1047 # See sample .bats scripts for examples. The idea is to list a set of 1048 # tests in a table, then use simple logic to iterate over each test. 1049 # Columns are separated using '|' (pipe character) because sometimes 1050 # we need spaces in our fields. 1051 # 1052 function parse_table() { 1053 while read line; do 1054 test -z "$line" && continue 1055 1056 declare -a row=() 1057 while read col; do 1058 dprint "col=<<$col>>" 1059 row+=("$col") 1060 done < <(echo "$line" | sed -E -e 's/(^|\s)\|(\s|$)/\n /g' | sed -e 's/^ *//' -e 's/\\/\\\\/g') 1061 # the above seds: 1062 # 1) Convert '|' to newline, but only if bracketed by spaces or 1063 # at beginning/end of line (this allows 'foo|bar' in tests); 1064 # 2) then remove leading whitespace; 1065 # 3) then double-escape all backslashes 1066 1067 printf "%q " "${row[@]}" 1068 printf "\n" 1069 done <<<"$1" 1070 } 1071 1072 1073 ################### 1074 # random_string # Returns a pseudorandom human-readable string 1075 ################### 1076 # 1077 # Numeric argument, if present, is desired length of string 1078 # 1079 function random_string() { 1080 local length=${1:-10} 1081 1082 head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length 1083 } 1084 1085 ######################### 1086 # find_exec_pid_files # Returns nothing or exec_pid hash files 1087 ######################### 1088 # 1089 # Return exec_pid hash files if exists, otherwise, return nothing 1090 # 1091 function find_exec_pid_files() { 1092 run_podman info --format '{{.Store.RunRoot}}' 1093 local storage_path="$output" 1094 if [ -d $storage_path ]; then 1095 find $storage_path -type f -iname 'exec_pid_*' 1096 fi 1097 } 1098 1099 1100 ############################# 1101 # remove_same_dev_warning # Filter out useless warning from output 1102 ############################# 1103 # 1104 # On some CI systems, 'podman run --privileged' emits a useless warning: 1105 # 1106 # WARNING: The same type, major and minor should not be used for multiple devices. 1107 # 1108 # This obviously screws us up when we look at output results. 1109 # 1110 # This function removes the warning from $output and $lines. We don't 1111 # do a full string match because there's another variant of that message: 1112 # 1113 # WARNING: Creating device "/dev/null" with same type, major and minor as existing "/dev/foodevdir/null". 1114 # 1115 # (We should never again see that precise error ever again, but we could 1116 # see variants of it). 1117 # 1118 function remove_same_dev_warning() { 1119 # No input arguments. We operate in-place on $output and $lines 1120 1121 local i=0 1122 local -a new_lines=() 1123 while [[ $i -lt ${#lines[@]} ]]; do 1124 if expr "${lines[$i]}" : 'WARNING: .* same type, major' >/dev/null; then 1125 : 1126 else 1127 new_lines+=("${lines[$i]}") 1128 fi 1129 i=$(( i + 1 )) 1130 done 1131 1132 lines=("${new_lines[@]}") 1133 output=$(printf '%s\n' "${lines[@]}") 1134 } 1135 1136 # run 'podman help', parse the output looking for 'Available Commands'; 1137 # return that list. 1138 function _podman_commands() { 1139 dprint "$@" 1140 # &>/dev/null prevents duplicate output 1141 run_podman help "$@" &>/dev/null 1142 awk '/^Available Commands:/{ok=1;next}/^Options:/{ok=0}ok { print $1 }' <<<"$output" | grep . 1143 } 1144 1145 ########################## 1146 # sleep_to_next_second # Sleep until second rolls over 1147 ########################## 1148 1149 function sleep_to_next_second() { 1150 sleep 0.$(printf '%04d' $((10000 - 10#$(date +%4N)))) 1151 } 1152 1153 function wait_for_command_output() { 1154 local cmd="$1" 1155 local want="$2" 1156 local tries=20 1157 local sleep_delay=0.5 1158 1159 case "${#*}" in 1160 2) ;; 1161 4) tries="$3" 1162 sleep_delay="$4" 1163 ;; 1164 *) die "Internal error: 'wait_for_command_output' requires two or four arguments" ;; 1165 esac 1166 1167 while [[ $tries -gt 0 ]]; do 1168 echo "$_LOG_PROMPT $cmd" 1169 run $cmd 1170 echo "$output" 1171 if [[ "$output" = "$want" ]]; then 1172 return 1173 fi 1174 1175 sleep $sleep_delay 1176 tries=$((tries - 1)) 1177 done 1178 die "Timed out waiting for '$cmd' to return '$want'" 1179 } 1180 1181 # END miscellaneous tools 1182 ###############################################################################