github.com/containers/podman/v5@v5.1.0-rc1/test/compose/test-compose (about) 1 #!/usr/bin/env bash 2 # 3 # Usage: test-compose [testname] 4 # 5 set -Eu 6 ME=$(basename $0) 7 8 ############################################################################### 9 # BEGIN stuff you can but probably shouldn't customize 10 11 # Directory where this script and all subtests live 12 TEST_ROOTDIR=$(realpath $(dirname $0)) 13 14 # Podman executable 15 PODMAN_BIN=$(realpath $TEST_ROOTDIR/../../bin)/podman 16 17 # Local path to docker socket with unix prefix 18 # The path will be changed for rootless users 19 DOCKER_SOCK=/var/run/docker.sock 20 21 # END stuff you can but probably shouldn't customize 22 ############################################################################### 23 # BEGIN setup 24 25 export TMPDIR=${TMPDIR:-/var/tmp} 26 WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX) 27 28 # Log of all HTTP requests and responses; always make '.log' point to latest 29 LOGBASE=${TMPDIR}/$ME.log 30 LOG=${LOGBASE}.$(date +'%Y%m%dT%H%M%S') 31 ln -sf $LOG $LOGBASE 32 33 # Keep track of test count and failures in files, not variables, because 34 # variables don't carry back up from subshells. 35 testcounter_file=$WORKDIR/.testcounter 36 failures_file=$WORKDIR/.failures 37 38 echo 0 >$testcounter_file 39 echo 0 >$failures_file 40 41 # END setup 42 ############################################################################### 43 # BEGIN infrastructure code - the helper functions used in tests themselves 44 45 ################# 46 # is_rootless # Check if we run as normal user 47 ################# 48 function is_rootless() { 49 [ "$(id -u)" -ne 0 ] 50 } 51 52 ######### 53 # die # Exit error with a message to stderr 54 ######### 55 function die() { 56 echo "$ME: $*" >&2 57 exit 1 58 } 59 60 ######## 61 # is # Simple comparison 62 ######## 63 function is() { 64 local actual=$1 65 local expect=$2 66 local testname=$3 67 68 if [[ "$actual" = "$expect" ]]; then 69 # On success, include expected value; this helps readers understand 70 _show_ok 1 "$testname=$expect" 71 return 72 fi 73 _show_ok 0 "$testname" "$expect" "$actual" 74 } 75 76 ########## 77 # like # Compare, but allowing patterns 78 ########## 79 function like() { 80 local actual=$1 81 local expect=$2 82 local testname=$3 83 84 # "is" (equality) is a subset of "like", but one that expr fails on if 85 # the expected result has shell-special characters like '['. Treat it 86 # as a special case. 87 88 if [[ "$actual" = "$expect" ]]; then 89 _show_ok 1 "$testname=$expect" 90 return 91 fi 92 93 if expr "$actual" : ".*$expect" &>/dev/null; then 94 # On success, include expected value; this helps readers understand 95 _show_ok 1 "$testname ('$actual') ~ $expect" 96 return 97 fi 98 _show_ok 0 "$testname" "~ $expect" "$actual" 99 } 100 101 ############## 102 # _show_ok # Helper for is() and like(): displays 'ok' or 'not ok' 103 ############## 104 function _show_ok() { 105 local ok=$1 106 local testname=$2 107 108 # If output is a tty, colorize pass/fail 109 local red= 110 local green= 111 local reset= 112 local bold= 113 if [ -t 1 ]; then 114 red='\e[31m' 115 green='\e[32m' 116 reset='\e[0m' 117 bold='\e[1m' 118 fi 119 120 _bump $testcounter_file 121 count=$(<$testcounter_file) 122 123 # "skip" is a special case of "ok". Assume that our caller has included 124 # the magical '# skip - reason" comment string. 125 if [[ $ok == "skip" ]]; then 126 # colon-plus: replace green with yellow, but only if green is non-null 127 green="${green:+\e[33m}" 128 ok=1 129 fi 130 if [ $ok -eq 1 ]; then 131 echo -e "${green}ok $count $testname${reset}" 132 echo "ok $count $testname" >>$LOG 133 return 134 fi 135 136 # Failed 137 local expect=$3 138 local actual=$4 139 printf "${red}not ok $count $testname${reset}\n" 140 # Not all errors include actual/expect 141 if [[ -n "$expect" || -n "$actual" ]]; then 142 printf "${red}# expected: %s${reset}\n" "$expect" 143 printf "${red}# actual: ${bold}%s${reset}\n" "$actual" 144 fi 145 146 echo "not ok $count $testname" >>$LOG 147 echo " expected: $expect" >>$LOG 148 149 _bump $failures_file 150 } 151 152 ########### 153 # _bump # Increment a counter in a file 154 ########### 155 function _bump() { 156 local file=$1 157 158 count=$(<$file) 159 echo $(( $count + 1 )) >| $file 160 } 161 162 ############### 163 # test_port # Run curl against a port, check results against expectation 164 ############### 165 function test_port() { 166 local port="$1" # e.g. 5000 167 local op="$2" # '=' or '~' 168 local expect="$3" # what to expect from curl output 169 170 # -s -S means "silent, but show errors" 171 local actual 172 actual=$(curl --retry 3 --retry-all-errors -s -S http://127.0.0.1:$port/) 173 local curl_rc=$? 174 175 if [ $curl_rc -ne 0 ]; then 176 _show_ok 0 "$testname - curl (port $port) failed with status $curl_rc" 177 echo "# cat $WORKDIR/server.log:" 178 cat $WORKDIR/server.log 179 echo "# cat $logfile:" 180 cat $logfile 181 return 182 fi 183 184 case "$op" in 185 '=') is "$actual" "$expect" "$testname : port $port" ;; 186 '~') like "$actual" "$expect" "$testname : port $port" ;; 187 *) die "Invalid operator '$op'" ;; 188 esac 189 } 190 191 192 ################### 193 # start_service # Run the socket listener 194 ################### 195 service_pid= 196 function start_service() { 197 test -x $PODMAN_BIN || die "Not found: $PODMAN_BIN" 198 199 # FIXME: use ${testname} subdir but we can't: 50-char limit in runroot 200 if ! is_rootless; then 201 rm -rf $WORKDIR/{root,runroot,cni} 202 else 203 $PODMAN_BIN unshare rm -rf $WORKDIR/{root,runroot,cni} 204 fi 205 rm -f $DOCKER_SOCK 206 mkdir --mode 0755 $WORKDIR/{root,runroot,cni} 207 chcon --reference=/var/lib/containers $WORKDIR/root 208 209 $PODMAN_BIN \ 210 --log-level debug \ 211 --storage-driver=vfs \ 212 --root $WORKDIR/root \ 213 --runroot $WORKDIR/runroot \ 214 --cgroup-manager=systemd \ 215 --network-config-dir $WORKDIR/cni \ 216 system service \ 217 --time 0 unix://$DOCKER_SOCK \ 218 &> $WORKDIR/server.log & 219 service_pid=$! 220 221 # Wait (FIXME: how do we test the socket?) 222 local _timeout=5 223 while [ $_timeout -gt 0 ]; do 224 # FIXME: should we actually try a read or write? 225 test -S $DOCKER_SOCK && return 226 sleep 1 227 _timeout=$(( $_timeout - 1 )) 228 done 229 cat $WORKDIR/server.log 230 die "Timed out waiting for service" 231 } 232 233 ############ 234 # podman # Needed by some test scripts to invoke the actual podman binary 235 ############ 236 function podman() { 237 echo "\$ podman $*" >>$WORKDIR/output.log 238 output=$($PODMAN_BIN \ 239 --storage-driver=vfs \ 240 --root $WORKDIR/root \ 241 --runroot $WORKDIR/runroot \ 242 --network-config-dir $WORKDIR/cni \ 243 "$@") 244 rc=$? 245 246 echo -n "$output" >>$WORKDIR/output.log 247 return $rc 248 } 249 250 # as rootless we want to test the remote connection so we add --connection 251 function podman_compose() { 252 if is_rootless; then 253 $PODMAN_BIN --connection compose-sock compose "$@" 254 else 255 podman compose "$@" 256 fi 257 } 258 259 ################### 260 # random_string # Returns a pseudorandom human-readable string 261 ################### 262 function random_string() { 263 # Numeric argument, if present, is desired length of string 264 local length=${1:-10} 265 266 head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length 267 } 268 269 # END infrastructure code 270 ############################################################################### 271 # BEGIN sanity checks 272 273 for tool in curl docker-compose; do 274 type $tool &>/dev/null || die "$ME: Required tool '$tool' not found" 275 done 276 277 # END sanity checks 278 ############################################################################### 279 # BEGIN entry handler (subtest invoker) 280 281 # When rootless use a socket path accessible by the rootless user 282 if is_rootless; then 283 # lets test two cases here, for rootless we try to connect to the connection as this should be respected 284 DOCKER_SOCK="$WORKDIR/docker.sock" 285 # use PODMAN_CONNECTIONS_CONF so we do not overwrite user settings 286 PODMAN_CONNECTIONS_CONF="$WORKDIR/connections.json" 287 export PODMAN_CONNECTIONS_CONF 288 $PODMAN_BIN system connection add --default notexists "unix:///I/do/not/exist" 289 $PODMAN_BIN system connection add compose-sock "unix://$DOCKER_SOCK" 290 291 else 292 # export DOCKER_HOST docker-compose will use it 293 DOCKER_HOST="unix://$DOCKER_SOCK" 294 export DOCKER_HOST 295 fi 296 297 # hide annoying podman compose warnings, some tests want to check compose stderr and this breaks it. 298 CONTAINERS_CONF_OVERRIDE="$WORKDIR/containers.conf" 299 echo '[engine] 300 compose_warning_logs=false' > "$CONTAINERS_CONF_OVERRIDE" 301 export CONTAINERS_CONF_OVERRIDE 302 303 304 # Identify the tests to run. If called with args, use those as globs. 305 tests_to_run=() 306 if [ -n "$*" ]; then 307 shopt -s nullglob 308 for i; do 309 match=(${TEST_ROOTDIR}/*${i}*/docker-compose.yml) 310 if [ ${#match} -eq 0 ]; then 311 die "No match for $TEST_ROOTDIR/*$i*.curl" 312 fi 313 tests_to_run+=("${match[@]}") 314 done 315 shopt -u nullglob 316 else 317 tests_to_run=(${TEST_ROOTDIR}/*/docker-compose.yml) 318 fi 319 320 # Too hard to precompute the number of tests; just spit it out at the end. 321 n_tests=0 322 323 # We aren't really TAP 13; this helps logformatter recognize our output as BATS 324 echo "TAP version 13" 325 326 for t in "${tests_to_run[@]}"; do 327 testdir="$(dirname $t)" 328 testname="$(basename $testdir)" 329 330 if [ -e $testdir/SKIP ]; then 331 reason="$(<$testdir/SKIP)" 332 if [ -n "$reason" ]; then 333 reason=" - $reason" 334 fi 335 _show_ok skip "$testname # skip$reason" 336 continue 337 fi 338 339 start_service 340 341 logfile=$WORKDIR/$testname.log 342 ( 343 cd $testdir || die "Cannot cd $testdir" 344 345 if [ -e teardown.sh ]; then 346 trap 'teardown' EXIT 347 function teardown() { 348 trap '_show_ok 0 "$testname - teardown" "[ok]" "[error]"' ERR 349 . teardown.sh 350 trap - ERR 351 } 352 fi 353 354 # setup file may be used for creating temporary directories/files. 355 # We source it so that envariables defined in it will get back to us. 356 if [ -e setup.sh ]; then 357 trap '_show_ok 0 "$testname - setup" "[ok]" "[error]"' ERR 358 . setup.sh 359 trap - ERR 360 fi 361 362 podman_compose up -d &> $logfile 363 docker_compose_rc=$? 364 if [[ $docker_compose_rc -ne 0 ]]; then 365 _show_ok 0 "$testname - up" "[ok]" "status=$docker_compose_rc" 366 sed -e 's/^/# /' <$logfile 367 podman_compose down >>$logfile 2>&1 # No status check here 368 exit 1 369 fi 370 _show_ok 1 "$testname - up" 371 372 # Run tests. This is likely to be a series of 'test_port' checks 373 # but may also include podman commands to inspect labels, state 374 if [ -e tests.sh ]; then 375 trap '_show_ok 0 "$testname - tests" "[ok]" "[error]"' ERR 376 . tests.sh 377 trap - ERR 378 fi 379 # FIXME: if any tests fail, try 'podman logs' on container? 380 381 if [ -n "${COMPOSE_WAIT:-}" ]; then 382 echo -n "Pausing due to \$COMPOSE_WAIT. Press ENTER to continue: " 383 read keepgoing 384 fi 385 386 # Done. Clean up. 387 podman_compose down &>> $logfile 388 rc=$? 389 if [[ $rc -eq 0 ]]; then 390 _show_ok 1 "$testname - down" 391 else 392 _show_ok 0 "$testname - down" "[ok]" "rc=$rc" 393 # FIXME: show error 394 fi 395 ) 396 397 kill $service_pid 398 wait $service_pid 399 400 # FIXME: otherwise we get EBUSY 401 if ! is_rootless; then 402 umount $WORKDIR/root/overlay &>/dev/null 403 else 404 $PODMAN_BIN unshare umount $WORKDIR/root/overlay &>/dev/null 405 fi 406 407 # FIXME: run 'podman ps'? 408 # rm -rf $WORKDIR/${testname} 409 done 410 411 # END entry handler 412 ############################################################################### 413 414 # Clean up 415 416 test_count=$(<$testcounter_file) 417 failure_count=$(<$failures_file) 418 419 if [ -z "${PODMAN_TESTS_KEEP_WORKDIR:-}" ]; then 420 if ! is_rootless; then 421 rm -rf $WORKDIR 422 else 423 $PODMAN_BIN unshare rm -rf $WORKDIR 424 fi 425 fi 426 427 echo "1..${test_count}" 428 429 exit $failure_count