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