From f3e69d7300e3b4d6c5bb676c1dae27b10c3a4d56 Mon Sep 17 00:00:00 2001 From: Ed Santiago Date: Thu, 10 Dec 2020 14:34:21 -0700 Subject: test-compose: rewrite to new subdir form ...in which we use all-local tests Signed-off-by: Ed Santiago --- test/compose/README.md | 47 ++++ test/compose/elasticsearch-logstash-kibana.curl | 3 - test/compose/flask.curl | 1 - test/compose/gitea-postgres.curl | 1 - test/compose/react-rust-postgres.skip | 1 - test/compose/simple_port_map/frontend/app.py | 10 +- test/compose/simple_port_map/setup.sh | 3 + test/compose/simple_port_map/teardown.sh | 4 + test/compose/simple_port_map/tests.sh | 3 + test/compose/test-compose | 284 ++++++++---------------- 10 files changed, 160 insertions(+), 197 deletions(-) create mode 100644 test/compose/README.md delete mode 100644 test/compose/elasticsearch-logstash-kibana.curl delete mode 100644 test/compose/flask.curl delete mode 100644 test/compose/gitea-postgres.curl delete mode 100644 test/compose/react-rust-postgres.skip create mode 100644 test/compose/simple_port_map/setup.sh create mode 100644 test/compose/simple_port_map/teardown.sh create mode 100644 test/compose/simple_port_map/tests.sh (limited to 'test') diff --git a/test/compose/README.md b/test/compose/README.md new file mode 100644 index 000000000..863decf2c --- /dev/null +++ b/test/compose/README.md @@ -0,0 +1,47 @@ +Tests for docker-compose +======================== + +This directory contains tests for docker-compose under podman. + +Each subdirectory must contain one docker-compose.yml file along with +all necessary infrastructure for it (e.g. Containerfile, any files +to be copied into the container, and so on. + +The `test-compose` script will, for each test subdirectory: + +* set up a fresh podman root under an empty working directory; +* run a podman server rooted therein; +* cd to the test subdirectory, and run `docker-compose up -d`; +* source `tests.sh`; +* run `docker-compose down`. + +As a special case, `setup.sh` and `teardown.sh` in the test directory +will contain commands to be executed prior to `docker-compose up` and +after `docker-compose down` respectively. + +tests.sh will probably contain commands of the form + + test_port 12345 = 'hello there' + +Where 12345 is the port to curl to; '=' checks equality, '~' uses `expr` +to check substrings; and 'hello there' is a string to look for in +the curl results. + +Usage: + + $ sudo test/compose/test-compose [pattern] + +By default, all subdirs will be run. If given a pattern, only those +subdirectories matching 'pattern' will be run. + +If `$COMPOSE_WAIT` is set, `test-compose` will pause before running +`docker-compose down`. This can be helpful for you to debug failing tests: + + $ env COMPOSE_WAIT=1 sudo --preserve-env=COMPOSE_WAIT test/compose/test-compose + +Then, in another window, + + # ls -lt /var/tmp/ + # X=/var/tmp/test-compose.tmp.XXXXXX <--- most recent results of above + # podman --root $X/root --runroot $X/runroot ps -a + # podman --root $X/root --runroot $X/runroot logs -l diff --git a/test/compose/elasticsearch-logstash-kibana.curl b/test/compose/elasticsearch-logstash-kibana.curl deleted file mode 100644 index ddb7a96b0..000000000 --- a/test/compose/elasticsearch-logstash-kibana.curl +++ /dev/null @@ -1,3 +0,0 @@ -9200 You Know, for Search -9600 "status":"green" -5601 Kibana diff --git a/test/compose/flask.curl b/test/compose/flask.curl deleted file mode 100644 index b50ddbf1d..000000000 --- a/test/compose/flask.curl +++ /dev/null @@ -1 +0,0 @@ -5000 Hello World! diff --git a/test/compose/gitea-postgres.curl b/test/compose/gitea-postgres.curl deleted file mode 100644 index a0a58b3fd..000000000 --- a/test/compose/gitea-postgres.curl +++ /dev/null @@ -1 +0,0 @@ -3000 .* Gitea: Git with a cup of tea diff --git a/test/compose/react-rust-postgres.skip b/test/compose/react-rust-postgres.skip deleted file mode 100644 index 57f89ed17..000000000 --- a/test/compose/react-rust-postgres.skip +++ /dev/null @@ -1 +0,0 @@ -broken diff --git a/test/compose/simple_port_map/frontend/app.py b/test/compose/simple_port_map/frontend/app.py index 895556a89..97b17c440 100644 --- a/test/compose/simple_port_map/frontend/app.py +++ b/test/compose/simple_port_map/frontend/app.py @@ -1,9 +1,15 @@ from flask import Flask +import os app = Flask(__name__) @app.route('/') def hello(): - return "Podman rulez!" + passthru = "ERROR: Could not get $ENV_PASSTHRU envariable" + try: + passthru = os.getenv("ENV_PASSTHRU") + except Exception as e: + passthru = passthru + ": " + str(e) + return "Podman rulez!--" + passthru + "--!" if __name__ == '__main__': - app.run(host='0.0.0.0') + app.run(host='0.0.0.0') diff --git a/test/compose/simple_port_map/setup.sh b/test/compose/simple_port_map/setup.sh new file mode 100644 index 000000000..9004b1e76 --- /dev/null +++ b/test/compose/simple_port_map/setup.sh @@ -0,0 +1,3 @@ +# -*- bash -*- + +export ENV_PASSTHRU=$(random_string 20) diff --git a/test/compose/simple_port_map/teardown.sh b/test/compose/simple_port_map/teardown.sh new file mode 100644 index 000000000..3f8153fa0 --- /dev/null +++ b/test/compose/simple_port_map/teardown.sh @@ -0,0 +1,4 @@ +# -*- bash -*- + +# FIXME: this is completely unnecessary; it's just an example of a teardown +unset ENV_PASSTHRU diff --git a/test/compose/simple_port_map/tests.sh b/test/compose/simple_port_map/tests.sh new file mode 100644 index 000000000..959b429d6 --- /dev/null +++ b/test/compose/simple_port_map/tests.sh @@ -0,0 +1,3 @@ +# -*- bash -*- + +test_port 5000 = "Podman rulez!--$ENV_PASSTHRU--!" diff --git a/test/compose/test-compose b/test/compose/test-compose index f7643b078..9558fbf58 100755 --- a/test/compose/test-compose +++ b/test/compose/test-compose @@ -1,32 +1,26 @@ #!/usr/bin/env bash # -# Usage: test-docker-compose [testname] -# -# DEVELOPER NOTE: you almost certainly don't need to play in here. See README. +# Usage: test-compose [testname] # ME=$(basename $0) ############################################################################### # BEGIN stuff you can but probably shouldn't customize -# Directory where this script (and extra test configs) live +# Directory where this script and all subtests live TEST_ROOTDIR=$(realpath $(dirname $0)) # Podman executable -PODMAN_BIN=$(realpath bin)/podman - -# Github repo containing sample docker-compose setups -# FIXME: we should probably version this -AWESOME_COMPOSE=https://github.com/docker/awesome-compose +PODMAN_BIN=$(realpath $TEST_ROOTDIR/../../bin)/podman -# Local path to docker socket +# Local path to docker socket (we will add the unix:/ prefix when we need it) DOCKER_SOCK=/var/run/docker.sock # END stuff you can but probably shouldn't customize ############################################################################### # BEGIN setup -TMPDIR=${TMPDIR:-/var/tmp} +export TMPDIR=${TMPDIR:-/var/tmp} WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX) # Log of all HTTP requests and responses; always make '.log' point to latest @@ -153,145 +147,30 @@ function _bump() { echo $(( $count + 1 )) >| $file } -############# -# jsonify # convert 'foo=bar,x=y' to json {"foo":"bar","x":"y"} -############# -function jsonify() { - # split by comma - local -a settings_in - read -ra settings_in <<<"$1" - - # convert each to double-quoted form - local -a settings_out - for i in ${settings_in[*]}; do - settings_out+=$(sed -e 's/\(.*\)=\(.*\)/"\1":"\2"/' <<<$i) - done - - # ...and wrap inside braces. - # FIXME: handle commas - echo "{${settings_out[*]}}" -} - -####### -# t # Main test helper -####### -function t() { - local method=$1; shift - local path=$1; shift - local curl_args - - local testname="$method $path" - # POST requests require an extra params arg - if [[ $method = "POST" ]]; then - curl_args="-d $(jsonify $1)" - testname="$testname [$curl_args]" - shift - fi - - # entrypoint path can include a descriptive comment; strip it off - path=${path%% *} - - # curl -X HEAD but without --head seems to wait for output anyway - if [[ $method == "HEAD" ]]; then - curl_args="--head" - fi - local expected_code=$1; shift - - # If given path begins with /, use it as-is; otherwise prepend /version/ - local url=http://$HOST:$PORT - if expr "$path" : "/" >/dev/null; then - url="$url$path" - else - url="$url/v1.40/$path" - fi - - # Log every action we do - echo "-------------------------------------------------------------" >>$LOG - echo "\$ $testname" >>$LOG - rm -f $WORKDIR/curl.* - # -s = silent, but --write-out 'format' gives us important response data - response=$(curl -s -X $method ${curl_args} \ - -H 'Content-type: application/json' \ - --dump-header $WORKDIR/curl.headers.out \ - --write-out '%{http_code}^%{content_type}^%{time_total}' \ - -o $WORKDIR/curl.result.out "$url") - - # Any error from curl is instant bad news, from which we can't recover - rc=$? - if [[ $rc -ne 0 ]]; then - echo "FATAL: curl failure ($rc) on $url - cannot continue" >&2 - exit 1 - fi - - # Show returned headers (without trailing ^M or empty lines) in log file. - # Sometimes -- I can't remember why! -- we don't get headers. - if [[ -e $WORKDIR/curl.headers.out ]]; then - tr -d '\015' < $WORKDIR/curl.headers.out | egrep '.' >>$LOG - fi - - IFS='^' read actual_code content_type time_total <<<"$response" - printf "X-Response-Time: ${time_total}s\n\n" >>$LOG - - # Log results, if text. If JSON, filter through jq for readability. - if [[ $content_type =~ /octet ]]; then - output="[$(file --brief $WORKDIR/curl.result.out)]" - echo "$output" >>$LOG - else - output=$(< $WORKDIR/curl.result.out) - - if [[ $content_type =~ application/json ]]; then - jq . <<<"$output" >>$LOG - else - echo "$output" >>$LOG - fi - fi - - # Test return code - is "$actual_code" "$expected_code" "$testname : status" - - # Special case: 204/304, by definition, MUST NOT return content (rfc2616) - if [[ $expected_code = 204 || $expected_code = 304 ]]; then - if [ -n "$*" ]; then - die "Internal error: ${expected_code} status returns no output; fix your test." - fi - if [ -n "$output" ]; then - _show_ok 0 "$testname: ${expected_code} status returns no output" "''" "$output" - fi - return - fi - - local i - - # Special case: if response code does not match, dump the response body - # and skip all further subtests. - if [[ $actual_code != $expected_code ]]; then - echo -e "# response: $output" - for i; do - _show_ok skip "$testname: $i # skip - wrong return code" - done - return +############### +# test_port # Run curl against a port, check results against expectation +############### +function test_port() { + local port="$1" # e.g. 5000 + local op="$2" # '=' or '~' + local expect="$3" # what to expect from curl output + + local actual=$(curl --retry 5 --retry-connrefused -s http://127.0.0.1:$port/) + local curl_rc=$? + if [ $curl_rc -ne 0 ]; then + _show_ok 0 "$testname - curl failed with status $curl_rc" +### docker-compose down >>$logfile 2>&1 +### exit 1 fi - for i; do - if expr "$i" : "[^=~]\+=.*" >/dev/null; then - # Exact match on json field - json_field=$(expr "$i" : "\([^=]*\)=") - expect=$(expr "$i" : '[^=]*=\(.*\)') - actual=$(jq -r "$json_field" <<<"$output") - is "$actual" "$expect" "$testname : $json_field" - elif expr "$i" : "[^=~]\+~.*" >/dev/null; then - # regex match on json field - json_field=$(expr "$i" : "\([^~]*\)~") - expect=$(expr "$i" : '[^~]*~\(.*\)') - actual=$(jq -r "$json_field" <<<"$output") - like "$actual" "$expect" "$testname : $json_field" - else - # Direct string comparison - is "$output" "$i" "$testname : output" - fi - done + case "$op" in + '=') is "$actual" "$expect" "$testname : port $port" ;; + '~') like "$actual" "$expect" "$testname : port $port" ;; + *) die "Invalid operator '$op'" ;; + esac } + ################### # start_service # Run the socket listener ################### @@ -299,8 +178,10 @@ service_pid= function start_service() { test -x $PODMAN_BIN || die "Not found: $PODMAN_BIN" - rm -rf $WORKDIR/{root,runroot,cni} - mkdir $WORKDIR/cni + # FIXME: use ${testname} subdir but we can't: 50-char limit in runroot + rm -rf $WORKDIR/{root,runroot,cni} + mkdir --mode 0755 $WORKDIR/{root,runroot,cni} + chcon --reference=/var/lib/containers $WORKDIR/root cp /etc/cni/net.d/*podman*conflist $WORKDIR/cni/ $PODMAN_BIN \ @@ -329,8 +210,21 @@ function start_service() { # podman # Needed by some test scripts to invoke the actual podman binary ############ function podman() { - echo "\$ $PODMAN_BIN $*" >>$WORKDIR/output.log - $PODMAN_BIN --root $WORKDIR "$@" >>$WORKDIR/output.log 2>&1 + echo "\$ podman $*" >>$WORKDIR/output.log + $PODMAN_BIN \ + --root $WORKDIR/root \ + --runroot $WORKDIR/runroot \ + "$@" >>$WORKDIR/output.log 2>&1 +} + +################### +# random_string # Returns a pseudorandom human-readable string +################### +function random_string() { + # Numeric argument, if present, is desired length of string + local length=${1:-10} + + head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length } # END infrastructure code @@ -345,17 +239,12 @@ done ############################################################################### # BEGIN entry handler (subtest invoker) -TESTS_DIR=$WORKDIR/awesome-compose - -git clone $AWESOME_COMPOSE $TESTS_DIR -git -C $TESTS_DIR checkout -q a3c38822277bcca04abbadf34120dcff808db3ec - # Identify the tests to run. If called with args, use those as globs. tests_to_run=() if [ -n "$*" ]; then shopt -s nullglob for i; do - match=(${TEST_ROOTDIR}/*${i}*.curl) + match=(${TEST_ROOTDIR}/*${i}*/docker-compose.yml) if [ ${#match} -eq 0 ]; then die "No match for $TEST_ROOTDIR/*$i*.curl" fi @@ -363,56 +252,68 @@ if [ -n "$*" ]; then done shopt -u nullglob else - tests_to_run=(${TEST_ROOTDIR}/*.curl) + tests_to_run=(${TEST_ROOTDIR}/*/docker-compose.yml) fi -# Test count: each of those tests might have a local set of subtests -n_tests=$((2 * ${#tests_to_run[*]})) +# Too hard to precompute the number of tests; just spit it out at the end. +n_tests=0 for t in ${tests_to_run[@]}; do - n_curls=$(wc -l $t | awk '{print $1}') - n_tests=$(( n_tests + n_curls )) -done + testdir="$(dirname $t)" + testname="$(basename $testdir)" - -echo "1..$n_tests" - -for t in ${tests_to_run[@]}; do - testname="$(basename $t .curl)" + if [ -e $test_dir/SKIP ]; then + local reason="$(<$test_dir/SKIP)" + if [ -n "$reason" ]; then + reason=" - $reason" + fi + _show_ok skip "$testname # skip$reason" + continue + fi start_service logfile=$WORKDIR/$testname.log ( - cd $TESTS_DIR/$testname || die "Cannot cd $TESTS_DIR/$testname" + cd $testdir || die "Cannot cd $testdir" + + # setup file may be used for creating temporary directories/files. + # We source it so that envariables defined in it will get back to us. + if [ -e setup.sh ]; then + . setup.sh + fi + if [ -e teardown.sh ]; then + trap '. teardown.sh' 0 + fi + docker-compose up -d &> $logfile - if [[ $? -ne 0 ]]; then - _show_ok 0 "$testname - up" "[ok]" "$(< $logfile)" - # FIXME: cat log + docker_compose_rc=$? + if [[ $docker_compose_rc -ne 0 ]]; then + _show_ok 0 "$testname - up" "[ok]" "status=$docker_compose_rc" + sed -e 's/^/# /' <$logfile docker-compose down >>$logfile 2>&1 # No status check here exit 1 fi _show_ok 1 "$testname - up" - # FIXME: run tests, e.g. curl - curls=$TEST_ROOTDIR/$testname.curl - if [[ -e $curls ]]; then - while read port expect; do - actual=$(curl --retry 5 --retry-connrefused -s http://127.0.0.1:$port/) - curl_rc=$? - if [ $curl_rc -ne 0 ]; then - _show_ok 0 "$testname - curl failed with status $curl_rc" - docker-compose down >>$logfile 2>&1 - exit 1 - fi - like "$actual" "$expect" "$testname : port $port" - done < $curls + # Run tests. This is likely to be a series of 'test_port' checks + # but may also include podman commands to inspect labels, state + if [ -e tests.sh ]; then + . tests.sh + fi + # FIXME: if any tests fail, try 'podman logs' on container? + + if [ -n "$COMPOSE_WAIT" ]; then + echo -n "Pausing due to \$COMPOSE_WAIT. Press ENTER to continue: " + read keepgoing fi + # Done. Clean up. docker-compose down &> $logfile - if [[ $? -eq 0 ]]; then + rc=$? + if [[ $rc -eq 0 ]]; then _show_ok 1 "$testname - down" else - _show_ok 0 "$testname - down" "[ok]" "$(< $logfile)" + _show_ok 0 "$testname - down" "[ok]" "rc=$rc" # FIXME: show error fi ) @@ -422,6 +323,9 @@ for t in ${tests_to_run[@]}; do # FIXME: otherwise we get EBUSY umount $WORKDIR/root/overlay &>/dev/null + + # FIXME: run 'podman ps'? +# rm -rf $WORKDIR/${testname} done # END entry handler @@ -432,8 +336,10 @@ done test_count=$(<$testcounter_file) failure_count=$(<$failures_file) -if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then - rm -rf $WORKDIR -fi +#if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then +# rm -rf $WORKDIR +#fi + +echo "1..${test_count}" exit $failure_count -- cgit v1.2.3-54-g00ecf