diff --git a/.github/workflows/ci-master-pr.yml b/.github/workflows/ci-master-pr.yml index a6729920..d2eb19e3 100644 --- a/.github/workflows/ci-master-pr.yml +++ b/.github/workflows/ci-master-pr.yml @@ -54,15 +54,13 @@ jobs: if: matrix.testenv == 'dev' run: | set -eux - docker compose -f docker-compose.yml -f docker-compose.build.yml up --build -d - docker compose -f docker-compose.test.yml --profile dev up + ./test/test.sh dev 1 1 - name: Integration test (prod) if: matrix.testenv == 'prod' run: | set -eux - docker compose -f docker-compose.example.yml -f docker-compose.example.build.yml up --build -d - docker compose -f docker-compose.test.yml --profile prod up + ./test/test.sh prod 1 1 build: strategy: diff --git a/README.md b/README.md index 85ba1ab1..e9ca6a74 100644 --- a/README.md +++ b/README.md @@ -133,11 +133,10 @@ docker exec -it $( docker compose ps -q heatmaps) php /heatmaps/generate.php #-- docker exec -it $( docker compose ps -q db ) sh # Test -docker compose -f docker-compose.test.yml --profile dev up +./test/test.sh dev 1 # Test production builds locally -docker compose -f docker-compose.example.yml -f docker-compose.example.build.yml up --build -docker compose -f docker-compose.test.yml --profile prod up +./test/test.sh prod 1 # Dump the DB docker exec $( docker compose ps -q db ) mysqldump -uroot -proot hlstatsxce | gzip > hlstatsxce.sql.gz diff --git a/docker-compose.test.yml b/docker-compose.test.yml deleted file mode 100644 index a975b7c8..00000000 --- a/docker-compose.test.yml +++ /dev/null @@ -1,137 +0,0 @@ -version: '2.2' -services: - test-ready: - profiles: - - dev - - prod - image: alpine:latest - networks: - - default - stop_signal: SIGKILL - entrypoint: - - /bin/sh - command: - - -c - - | - set -eu - - echo "Waiting for stack to be ready" - s=0 - while true; do - nc -vz -w 1 web 80 \ - && nc -vz -w 1 web 9000 \ - && nc -vz -w 1 db 3306 \ - && break || true - s=$$(( $$s + 1 )) - if [ "$$s" -eq 600 ]; then - exit 1 - fi - echo "Retrying in 3 seconds" - sleep 3 - done - - test-routes: - profiles: - - dev - - prod - image: alpine:latest - environment: - URLS: | - http://web/ 302 - http://web/css/spinner.gif 200 - http://web/hlstatsimg/ajax.gif 200 - http://web/includes/ 401 - http://web/pages/ 401 - http://web/pages/.htaccess 401 - http://web/styles/classic.css 200 - http://web/updater/ 401 - http://web/autocomplete.php 200 - http://web/config.php 401 - http://web/hlstats.php 200 - http://web/index.php 302 - http://web/ingame.php 200 - http://web/show_graph.php 200 - http://web/sig.php 200 - http://web/status.php 200 - http://web/trend_graph.php 200 - networks: - - default - depends_on: - test-ready: - condition: service_completed_successfully - stop_signal: SIGKILL - entrypoint: - - /bin/sh - command: - - -c - - | - set -eu - echo "$$URLS" | awk NF | while read -r i j; do - if wget -q -SO- "$$i" 2>&1 | grep "HTTP/1.1 $$j " > /dev/null; then - echo "PASS: $$i" - else - echo "FAIL: $$i" - exit 1 - fi - done - - test-endpoints: - profiles: - - prod - build: - dockerfile_inline: | - FROM alpine:latest - RUN apk add --no-cache curl - environment: - ENDPOINTS: | - web.example.com 200 - phpmyadmin.example.com 200 - network_mode: host - depends_on: - test-ready: - condition: service_completed_successfully - stop_signal: SIGKILL - entrypoint: - - /bin/sh - command: - - -c - - | - set -eu - echo "$$ENDPOINTS" | awk NF | while read -r i j; do - if curl --head -kL http://$$i --resolve $$i:80:127.0.0.1 --resolve $$i:443:127.0.0.1 2>&1 | grep "^HTTP/2 $$j " > /dev/null; then - echo "PASS: $$i" - else - echo "FAIL: $$i" - exit 1 - fi - done - - test-awards: &test-docker - profiles: - - dev - - prod - image: docker:cli - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - ./:/src:ro - networks: - - default - depends_on: - test-ready: - condition: service_completed_successfully - working_dir: /src - stop_signal: SIGKILL - entrypoint: - - /bin/sh - command: - - -c - - docker exec -i $( docker compose ps -q awards) sh -c /awards.sh - - test-heatmaps: - <<: *test-docker - command: - - -c - - docker exec -i $( docker compose ps -q heatmaps) php /heatmaps/generate.php - -networks: - default: diff --git a/test/.env b/test/.env new file mode 100644 index 00000000..fba7692a --- /dev/null +++ b/test/.env @@ -0,0 +1 @@ +COMPOSE_PROJECT_NAME=hlstatsx-community-edition diff --git a/test/docker-compose.yml b/test/docker-compose.yml new file mode 100644 index 00000000..d1154ca9 --- /dev/null +++ b/test/docker-compose.yml @@ -0,0 +1,33 @@ +version: '2.2' +services: + test-container-networking: + image: alpine:latest + volumes: + - ./:/test:ro + networks: + - default + stop_signal: SIGKILL + working_dir: /test + entrypoint: + - /bin/sh + command: + - -c + - | + sleep infinity + + test-host-networking: + image: alpine:latest + volumes: + - ./:/test:ro + network_mode: host + stop_signal: SIGKILL + working_dir: /test + entrypoint: + - /bin/sh + command: + - -c + - | + sleep infinity + +networks: + default: diff --git a/test/test-awards.sh b/test/test-awards.sh new file mode 100755 index 00000000..3631a932 --- /dev/null +++ b/test/test-awards.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +docker exec -i $( docker compose ps -q awards) sh -c /awards.sh diff --git a/test/test-endpoints.sh b/test/test-endpoints.sh new file mode 100755 index 00000000..f3533460 --- /dev/null +++ b/test/test-endpoints.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -eu + +echo "[test-endpoints]" +ENDPOINTS=" +web.example.com 200 +phpmyadmin.example.com 200 +" +command -v curl || apk add --no-cache curl +echo "$ENDPOINTS" | awk NF | while read -r i j; do + if curl --head -skL http://$i --resolve $i:80:127.0.0.1 --resolve $i:443:127.0.0.1 2>&1 | grep "^HTTP/2 $j " > /dev/null; then + echo "PASS: $i" + else + echo "FAIL: $i" + exit 1 + fi +done diff --git a/test/test-heatmaps.sh b/test/test-heatmaps.sh new file mode 100755 index 00000000..594c86fe --- /dev/null +++ b/test/test-heatmaps.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +docker exec -i $( docker compose ps -q heatmaps) php /heatmaps/generate.php diff --git a/test/test-ready.sh b/test/test-ready.sh new file mode 100755 index 00000000..0d0a4690 --- /dev/null +++ b/test/test-ready.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -eu + +echo +echo "[test-ready] Waiting for stack to be ready" +s=0 +while true; do + nc -vz -w 1 web 80 \ + && nc -vz -w 1 web 9000 \ + && nc -vz -w 1 db 3306 \ + && break || true + s=$(( $s + 1 )) + if [ "$s" -eq 600 ]; then + exit 1 + fi + echo "Retrying in 3 seconds" + sleep 3 +done diff --git a/test/test-routes.sh b/test/test-routes.sh new file mode 100755 index 00000000..01c4fdd1 --- /dev/null +++ b/test/test-routes.sh @@ -0,0 +1,31 @@ +#!/bin/sh +set -eu + +echo "[test-routes]" +URLS=" +http://web/ 302 +http://web/css/spinner.gif 200 +http://web/hlstatsimg/ajax.gif 200 +http://web/includes/ 401 +http://web/pages/ 401 +http://web/pages/.htaccess 401 +http://web/styles/classic.css 200 +http://web/updater/ 401 +http://web/autocomplete.php 200 +http://web/config.php 401 +http://web/hlstats.php 200 +http://web/index.php 302 +http://web/ingame.php 200 +http://web/show_graph.php 200 +http://web/sig.php 200 +http://web/status.php 200 +http://web/trend_graph.php 200 +" +echo "$URLS" | awk NF | while read -r i j; do + if wget -q -SO- "$i" 2>&1 | grep "HTTP/1.1 $j " > /dev/null; then + echo "PASS: $i" + else + echo "FAIL: $i" + exit 1 + fi +done diff --git a/test/test.sh b/test/test.sh new file mode 100755 index 00000000..7e4755a8 --- /dev/null +++ b/test/test.sh @@ -0,0 +1,79 @@ +#!/bin/sh +set -eu + +TEST=${1:-} # Test environment +UP=${2:-} # Whether to docker compose up and down the test stack +CACHE=${3:-} # Whether to override with docker-compose.build.yml + +# Validation and normalization +if ! echo "$TEST" | grep -E '^(dev|prod)$' > /dev/null; then + echo "Specify TEST as the first argument. E.g. 'dev', 'prod'" + exit 1 +fi +if [ -n "$CACHE" ]; then + CACHE='-f docker-compose.build.yml' +fi + +SCRIPT_DIR=$( cd "$( dirname "$0" )" && pwd ) +ERR= +setup_test() { + cd "$SCRIPT_DIR" + docker compose up -d + if [ -n "$UP" ]; then + setup + fi + run +} +cleanup_test() { + ERR=$? + if [ -n "$UP" ]; then + cleanup + fi + docker compose stop + if [ -z "$ERR" ] || [ "$ERR" = 0 ]; then + echo "All tests succeeded" + else + echo "Some tests failed" + echo "Exit code: $ERR" + exit "$ERR" + fi +} +trap cleanup_test INT TERM EXIT + +echo "Testing..." +if [ "$TEST" = 'dev' ]; then + setup() { + if [ -n "$CACHE" ]; then + CACHE='-f docker-compose.build.yml' + fi + (cd .. && docker compose -f docker-compose.yml $CACHE up --build -d) + } + run() { + docker exec $( docker compose ps -q test-container-networking ) ./test-ready.sh + docker exec $( docker compose ps -q test-container-networking ) ./test-routes.sh + ./test-awards.sh + ./test-heatmaps.sh + } + cleanup() { + (cd .. && docker compose -f docker-compose.yml $CACHE stop) + } +fi +if [ "$TEST" = 'prod' ]; then + setup() { + if [ -n "$CACHE" ]; then + CACHE='-f docker-compose.example.build.yml' + fi + (cd .. && docker compose -f docker-compose.example.yml $CACHE up --build -d) + } + run() { + docker exec $( docker compose ps -q test-container-networking ) ./test-ready.sh + docker exec $( docker compose ps -q test-container-networking ) ./test-routes.sh + ./test-awards.sh + ./test-heatmaps.sh + docker exec $( docker compose ps -q test-host-networking ) ./test-endpoints.sh + } + cleanup() { + (cd .. && docker compose -f docker-compose.example.yml $CACHE stop) + } +fi +setup_test