github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/test/apiv2/test-apiv2 (about) 1 #!/usr/bin/env bash 2 # 3 # Usage: test-apiv2 [PORT] 4 # 5 # DEVELOPER NOTE: you almost certainly don't need to play in here. See README. 6 # 7 ME=$(basename $0) 8 9 ############################################################################### 10 # BEGIN stuff you can but probably shouldn't customize 11 12 PODMAN_TEST_IMAGE_REGISTRY=${PODMAN_TEST_IMAGE_REGISTRY:-"quay.io"} 13 PODMAN_TEST_IMAGE_USER=${PODMAN_TEST_IMAGE_USER:-"libpod"} 14 PODMAN_TEST_IMAGE_NAME=${PODMAN_TEST_IMAGE_NAME:-"alpine_labels"} 15 PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"latest"} 16 PODMAN_TEST_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG" 17 18 IMAGE=$PODMAN_TEST_IMAGE_FQN 19 20 # END stuff you can but probably shouldn't customize 21 ############################################################################### 22 # BEGIN setup 23 24 TMPDIR=${TMPDIR:-/tmp} 25 WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX) 26 27 # Log of all HTTP requests and responses; always make '.log' point to latest 28 LOGBASE=${TMPDIR}/$ME.log 29 LOG=${LOGBASE}.$(date +'%Y%m%dT%H%M%S') 30 ln -sf $LOG $LOGBASE 31 32 HOST=localhost 33 PORT=${PODMAN_SERVICE_PORT:-8081} 34 35 # Keep track of test count and failures in files, not variables, because 36 # variables don't carry back up from subshells. 37 testcounter_file=$WORKDIR/.testcounter 38 failures_file=$WORKDIR/.failures 39 40 echo 0 >$testcounter_file 41 echo 0 >$failures_file 42 43 # Where the tests live 44 TESTS_DIR=$(realpath $(dirname $0)) 45 46 # Path to podman binary 47 PODMAN_BIN=${PODMAN:-${TESTS_DIR}/../../bin/podman} 48 49 # END setup 50 ############################################################################### 51 # BEGIN infrastructure code - the helper functions used in tests themselves 52 53 ######### 54 # die # Exit error with a message to stderr 55 ######### 56 function die() { 57 echo "$ME: $*" >&2 58 exit 1 59 } 60 61 ######## 62 # is # Simple comparison 63 ######## 64 function is() { 65 local actual=$1 66 local expect=$2 67 local testname=$3 68 69 if [ "$actual" = "$expect" ]; then 70 # On success, include expected value; this helps readers understand 71 _show_ok 1 "$testname=$expect" 72 return 73 fi 74 _show_ok 0 "$testname" "$expect" "$actual" 75 } 76 77 ########## 78 # like # Compare, but allowing patterns 79 ########## 80 function like() { 81 local actual=$1 82 local expect=$2 83 local testname=$3 84 85 if expr "$actual" : "$expect" &>/dev/null; then 86 # On success, include expected value; this helps readers understand 87 _show_ok 1 "$testname ('$actual') ~ $expect" 88 return 89 fi 90 _show_ok 0 "$testname" "~ $expect" "$actual" 91 } 92 93 ############## 94 # _show_ok # Helper for is() and like(): displays 'ok' or 'not ok' 95 ############## 96 function _show_ok() { 97 local ok=$1 98 local testname=$2 99 100 # If output is a tty, colorize pass/fail 101 local red= 102 local green= 103 local reset= 104 local bold= 105 if [ -t 1 ]; then 106 red='\e[31m' 107 green='\e[32m' 108 reset='\e[0m' 109 bold='\e[1m' 110 fi 111 112 _bump $testcounter_file 113 count=$(<$testcounter_file) 114 115 # "skip" is a special case of "ok". Assume that our caller has included 116 # the magical '# skip - reason" comment string. 117 if [[ $ok == "skip" ]]; then 118 # colon-plus: replace green with yellow, but only if green is non-null 119 green="${green:+\e[33m}" 120 ok=1 121 fi 122 if [ $ok -eq 1 ]; then 123 echo -e "${green}ok $count ${TEST_CONTEXT} $testname${reset}" 124 echo "ok $count ${TEST_CONTEXT} $testname" >>$LOG 125 return 126 fi 127 128 # Failed 129 local expect=$3 130 local actual=$4 131 echo -e "${red}not ok $count ${TEST_CONTEXT} $testname${reset}" 132 echo -e "${red}# expected: $expect${reset}" 133 echo -e "${red}# actual: ${bold}$actual${reset}" 134 135 echo "not ok $count ${TEST_CONTEXT} $testname" >>$LOG 136 echo " expected: $expect" >>$LOG 137 138 _bump $failures_file 139 } 140 141 ########### 142 # _bump # Increment a counter in a file 143 ########### 144 function _bump() { 145 local file=$1 146 147 count=$(<$file) 148 echo $(( $count + 1 )) >| $file 149 } 150 151 ############# 152 # jsonify # convert 'foo=bar,x=y' to json {"foo":"bar","x":"y"} 153 ############# 154 function jsonify() { 155 # split by comma 156 local -a settings_in 157 read -ra settings_in <<<"$1" 158 159 # convert each to double-quoted form 160 local -a settings_out 161 for i in ${settings_in[*]}; do 162 settings_out+=$(sed -e 's/\(.*\)=\(.*\)/"\1":"\2"/' <<<$i) 163 done 164 165 # ...and wrap inside braces. 166 # FIXME: handle commas 167 echo "{${settings_out[*]}}" 168 } 169 170 ####### 171 # t # Main test helper 172 ####### 173 function t() { 174 local method=$1; shift 175 local path=$1; shift 176 local curl_args 177 178 local testname="$method $path" 179 # POST requests require an extra params arg 180 if [[ $method = "POST" ]]; then 181 curl_args="-d $(jsonify $1)" 182 testname="$testname [$curl_args]" 183 shift 184 fi 185 186 # entrypoint path can include a descriptive comment; strip it off 187 path=${path%% *} 188 189 # curl -X HEAD but without --head seems to wait for output anyway 190 if [[ $method == "HEAD" ]]; then 191 curl_args="--head" 192 fi 193 local expected_code=$1; shift 194 195 # If given path begins with /, use it as-is; otherwise prepend /version/ 196 local url=http://$HOST:$PORT 197 if expr "$path" : "/" >/dev/null; then 198 url="$url$path" 199 else 200 url="$url/v1.40/$path" 201 fi 202 203 # Log every action we do 204 echo "-------------------------------------------------------------" >>$LOG 205 echo "\$ $testname" >>$LOG 206 rm -f $WORKDIR/curl.* 207 # -s = silent, but --write-out 'format' gives us important response data 208 response=$(curl -s -X $method ${curl_args} \ 209 -H 'Content-type: application/json' \ 210 --dump-header $WORKDIR/curl.headers.out \ 211 --write-out '%{http_code}^%{content_type}^%{time_total}' \ 212 -o $WORKDIR/curl.result.out "$url") 213 214 # Any error from curl is instant bad news, from which we can't recover 215 rc=$? 216 if [[ $rc -ne 0 ]]; then 217 echo "FATAL: curl failure ($rc) on $url - cannot continue" >&2 218 exit 1 219 fi 220 221 # Show returned headers (without trailing ^M or empty lines) in log file. 222 # Sometimes -- I can't remember why! -- we don't get headers. 223 if [[ -e $WORKDIR/curl.headers.out ]]; then 224 tr -d '\015' < $WORKDIR/curl.headers.out | egrep '.' >>$LOG 225 fi 226 227 IFS='^' read actual_code content_type time_total <<<"$response" 228 printf "X-Response-Time: ${time_total}s\n\n" >>$LOG 229 230 # Log results, if text. If JSON, filter through jq for readability. 231 if [[ $content_type =~ /octet ]]; then 232 output="[$(file --brief $WORKDIR/curl.result.out)]" 233 echo "$output" >>$LOG 234 else 235 output=$(< $WORKDIR/curl.result.out) 236 237 if [[ $content_type =~ application/json ]]; then 238 jq . <<<"$output" >>$LOG 239 else 240 echo "$output" >>$LOG 241 fi 242 fi 243 244 # Test return code 245 is "$actual_code" "$expected_code" "$testname : status" 246 247 # Special case: 204/304, by definition, MUST NOT return content (rfc2616) 248 if [[ $expected_code = 204 || $expected_code = 304 ]]; then 249 if [ -n "$*" ]; then 250 die "Internal error: ${expected_code} status returns no output; fix your test." 251 fi 252 if [ -n "$output" ]; then 253 _show_ok 0 "$testname: ${expected_code} status returns no output" "''" "$output" 254 fi 255 return 256 fi 257 258 local i 259 260 # Special case: if response code does not match, dump the response body 261 # and skip all further subtests. 262 if [[ $actual_code != $expected_code ]]; then 263 echo -e "# response: $output" 264 for i; do 265 _show_ok skip "$testname: $i # skip - wrong return code" 266 done 267 return 268 fi 269 270 for i; do 271 if expr "$i" : "[^=~]\+=.*" >/dev/null; then 272 # Exact match on json field 273 json_field=$(expr "$i" : "\([^=]*\)=") 274 expect=$(expr "$i" : '[^=]*=\(.*\)') 275 actual=$(jq -r "$json_field" <<<"$output") 276 is "$actual" "$expect" "$testname : $json_field" 277 elif expr "$i" : "[^=~]\+~.*" >/dev/null; then 278 # regex match on json field 279 json_field=$(expr "$i" : "\([^~]*\)~") 280 expect=$(expr "$i" : '[^~]*~\(.*\)') 281 actual=$(jq -r "$json_field" <<<"$output") 282 like "$actual" "$expect" "$testname : $json_field" 283 else 284 # Direct string comparison 285 is "$output" "$i" "$testname : output" 286 fi 287 done 288 } 289 290 ################### 291 # start_service # Run the socket listener 292 ################### 293 service_pid= 294 function start_service() { 295 # If there's a listener on the port, nothing for us to do 296 { exec 3<> /dev/tcp/$HOST/$PORT; } &>/dev/null && return 297 298 test -x $PODMAN_BIN || die "Not found: $PODMAN_BIN" 299 300 if [ "$HOST" != "localhost" ]; then 301 die "Cannot start service on non-localhost ($HOST)" 302 fi 303 304 $PODMAN_BIN --root $WORKDIR system service --time 15 tcp:127.0.0.1:$PORT \ 305 &> $WORKDIR/server.log & 306 service_pid=$! 307 308 # Wait 309 local _timeout=5 310 while [ $_timeout -gt 0 ]; do 311 { exec 3<> /dev/tcp/$HOST/$PORT; } &>/dev/null && return 312 sleep 1 313 _timeout=$(( $_timeout - 1 )) 314 done 315 die "Timed out waiting for service" 316 } 317 318 ############ 319 # podman # Needed by some test scripts to invoke the actual podman binary 320 ############ 321 function podman() { 322 echo "\$ $PODMAN_BIN $*" >>$WORKDIR/output.log 323 $PODMAN_BIN --root $WORKDIR "$@" >>$WORKDIR/output.log 2>&1 324 } 325 326 #################### 327 # root, rootless # Is server rootless? 328 #################### 329 ROOTLESS= 330 function root() { 331 ! rootless 332 } 333 334 function rootless() { 335 if [[ -z $ROOTLESS ]]; then 336 ROOTLESS=$(curl -s http://$HOST:$PORT/v1.40/info | jq .Rootless) 337 fi 338 test "$ROOTLESS" = "true" 339 } 340 341 # True if cgroups v2 are enabled 342 function have_cgroupsv2() { 343 cgroup_type=$(stat -f -c %T /sys/fs/cgroup) 344 test "$cgroup_type" = "cgroup2fs" 345 } 346 347 # END infrastructure code 348 ############################################################################### 349 # BEGIN sanity checks 350 351 for tool in curl jq podman; do 352 type $tool &>/dev/null || die "$ME: Required tool '$tool' not found" 353 done 354 355 # END sanity checks 356 ############################################################################### 357 # BEGIN entry handler (subtest invoker) 358 359 # Identify the tests to run. If called with args, use those as globs. 360 tests_to_run=() 361 if [ -n "$*" ]; then 362 shopt -s nullglob 363 for i; do 364 match=(${TESTS_DIR}/*${i}*.at) 365 if [ ${#match} -eq 0 ]; then 366 die "No match for $TESTS_DIR/*$i*.at" 367 fi 368 tests_to_run+=("${match[@]}") 369 done 370 shopt -u nullglob 371 else 372 tests_to_run=($TESTS_DIR/*.at) 373 fi 374 375 start_service 376 377 for i in ${tests_to_run[@]}; do 378 TEST_CONTEXT="[$(basename $i .at)]" 379 source $i 380 done 381 382 # END entry handler 383 ############################################################################### 384 385 # Clean up 386 387 if [ -n "$service_pid" ]; then 388 kill $service_pid 389 wait $service_pid 390 fi 391 392 test_count=$(<$testcounter_file) 393 failure_count=$(<$failures_file) 394 395 if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then 396 rm -rf $WORKDIR 397 fi 398 399 echo "1..${test_count}" 400 401 exit $failure_count