diff --git a/.travis.yml b/.travis.yml index 392b52e..e8a4731 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,8 @@ services: before_install: - docker build -t eficode/wait-for . + +script: + - npm install + - ./run_tests.sh - docker run eficode/wait-for diff --git a/Dockerfile b/Dockerfile index 412d8e1..f636844 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,5 @@ WORKDIR /app COPY . /app RUN npm install -CMD ./node_modules/.bin/bats wait-for.bats +# On launch, run the test suite via npm +CMD npm test diff --git a/README.md b/README.md index f1956f7..ca2d3a9 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,9 @@ When using this tool, you only need to pick the `wait-for` file as part of your ## Usage ``` -./wait-for host:port [-t timeout] [-- command args] +wait-for host:port [-t timeout] [-- command args] -q | --quiet Do not output any status messages + -l | --loose Execute subcommand even if the test times out -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout -- COMMAND ARGS Execute command with args after the test finishes ``` @@ -21,11 +22,19 @@ To check if [eficode.com](https://eficode.com) is available: ``` $ ./wait-for www.eficode.com:80 -- echo "Eficode site is up" - -Connection to www.eficode.com port 80 [tcp/http] succeeded! Eficode site is up ``` +The subcommand will be executed regardless if the service is up or not. If you wish to execute the subcommand only if the service is up, add the --strict argument. In this example, we will test port 81 on www.google.com which will fail: + +``` +$ ./wait-for www.google.com:81 --timeout=1 -- echo "google is up" +Operation timed out +$ ./wait-for www.google.com:81 --timeout=1 --loose -- echo "waited for google" +Operation timed out +waited for google +``` + To wait for database container to become available: diff --git a/package.json b/package.json index 977ac4a..7aea0e7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "wait-for", "version": "0.1.0", "scripts": { - "test": "./node_modules/.bin/bats wait-for.bats" + "test": "./run_tests.sh" }, "dependencies": { "bats": "^0.4.2" diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..7bc5257 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# Although it would be possible to just call this directly from the Dockerfile, +# centralizing tests in this file allows both the docker container and the +# CI machine to run the same set of tests for an additional datapoint -- +# which gives a better chance of turning up POSIX noncompliance + +./node_modules/.bin/bats wait-for.bats diff --git a/wait-for b/wait-for index ddfc39e..3b2241c 100755 --- a/wait-for +++ b/wait-for @@ -1,39 +1,71 @@ #!/bin/sh +OLD_TIMEOUT=$TIMEOUT +OLD_QUIET=$QUIET +OLD_PORT=$PORT +OLD_HOST=$HOST +OLD_LOOSE=$LOOSE + TIMEOUT=15 QUIET=0 +LOOSE=0 + +if ! which nc >/dev/null; then + echo "Netcat is not installed. This script requires netcat to work correctly." + exit 1 +fi echoerr() { if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi } +conditionally_output() { + if [ "$QUIET" -ne 1 ]; then + "$@" + else + "$@" > /dev/null 2>&1 + fi +} + usage() { exitcode="$1" cat << USAGE >&2 Usage: - $cmdname host:port [-t timeout] [-- command args] + $(basename $0) host:port [-t timeout] [-- command args] -q | --quiet Do not output any status messages + -l | --loose Execute subcommand even if the test times out -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout -- COMMAND ARGS Execute command with args after the test finishes USAGE exit "$exitcode" } +test_connection() { + conditionally_output echo "Testing connection to $1:$2..." + + # force a 1-second timeout on darwin (https://stackoverflow.com/a/20460402/2063546) + # POSIX-compliant string inclusion test https://stackoverflow.com/a/8811800/2063546 + if [ "${OSTYPE#*darwin*}" != "$OSTYPE" ] ; then + conditionally_output nc -z -w 1 -G 1 "$1" "$2" + else + conditionally_output nc -z -w 1 "$1" "$2" + fi +} + wait_for() { + local result for i in `seq $TIMEOUT` ; do - nc -z "$HOST" "$PORT" > /dev/null 2>&1 - + # use a 1-second timeout, but still sleep 0.1 seconds after just to be safe + test_connection "$HOST" "$PORT" result=$? - if [ $result -eq 0 ] ; then - if [ $# -gt 0 ] ; then - exec "$@" - fi - exit 0 - fi + if [ $result -eq 0 ] ; then break ; fi sleep 1 done - echo "Operation timed out" >&2 - exit 1 + [ $result -ne 0 ] && echoerr "Operation timed out" + if [ $result -eq 0 -o $LOOSE -eq 1 -a $# -gt 0 ] ; then + TIMEOUT=$OLD_TIMEOUT QUIET=$OLD_QUIET PORT=$OLD_PORT HOST=$OLD_HOST LOOSE=$OLD_LOOSE exec "$@" + fi + exit $result } while [ $# -gt 0 ] @@ -48,6 +80,10 @@ do QUIET=1 shift 1 ;; + -l | --loose) + LOOSE=1 + shift 1 + ;; -t) TIMEOUT="$2" if [ "$TIMEOUT" = "" ]; then break; fi diff --git a/wait-for.bats b/wait-for.bats index cbea6a4..a95f889 100644 --- a/wait-for.bats +++ b/wait-for.bats @@ -1,8 +1,8 @@ #!/usr/bin/env bats -@test "google should be immediately found" { - run ./wait-for google.com:80 -- echo 'success' - +@test "google should be immediately found, no output other than our own" { + run ./wait-for -q google.com:80 -- echo 'success' + [ "$output" = "success" ] } @@ -12,3 +12,27 @@ [ "$status" -ne 0 ] [ "$output" != "success" ] } + +@test "nonexistent server should start command if loose option is specified" { + run ./wait-for -q -t 1 -l noserver:9999 -- echo 'passable' 2>&1 + + [ "$status" -eq 0 ] + + [ "$output" = "passable" ] +} + +@test "preserve existing environment variables" { + TIMEOUT=mytimeout + QUIET=myquiet + HOST=myhost + PORT=myport + LOOSE=myloose + + run ./wait-for google.com:80 -- echo 'success' + + [ "$(echo $TIMEOUT)" = 'mytimeout' ] + [ "$(echo $QUIET)" = 'myquiet' ] + [ "$(echo $HOST)" = 'myhost' ] + [ "$(echo $PORT)" = 'myport' ] + [ "$(echo $LOOSE)" = 'myloose' ] +}