Add support to run docker based functional tests This change adds the support to run functional tests in a docker based environment. The test can be executed from the gerrit directory(in-tree) using the below command: bazel test plugins/remote-gerrit-account-cache:docker-tests Change-Id: I3331bcb31592e92b5e485c5e9ce89703f745e499
diff --git a/BUILD b/BUILD index 05321c7..0fb030b 100644 --- a/BUILD +++ b/BUILD
@@ -28,3 +28,16 @@ size = "small", srcs = ["src/test/always_pass_test.sh"], ) + +sh_test( + name = "docker-tests", + size = "medium", + srcs = ["test/docker/run.sh"], + args = [ + "--remote-gerrit-account-cache-jar", + "$(location :remote-gerrit-account-cache)", + ], + data = [plugin_name] + glob(["test/**"]), + local = True, + tags = ["docker"], +)
diff --git a/test/docker/docker-compose.yaml b/test/docker/docker-compose.yaml new file mode 100755 index 0000000..da93273 --- /dev/null +++ b/test/docker/docker-compose.yaml
@@ -0,0 +1,35 @@ +services: + + remote-gerrit: + build: + context: remote-gerrit + networks: + - gerrit-net + + gerrit: + build: + context: gerrit + args: + REMOTE_GERRIT_BASE_URL: "http://remote-gerrit:8080" + HTTP_USERNAME: "admin" + HTTP_PASSWORD: "secret" + networks: + - gerrit-net + depends_on: + - remote-gerrit + + run_tests: + build: + context: run_tests + networks: + - gerrit-net + depends_on: + - remote-gerrit + - gerrit + environment: + - REMOTE_GERRIT_HOST=remote-gerrit + - GERRIT_HOST=gerrit + +networks: + gerrit-net: + driver: bridge
diff --git a/test/docker/gerrit/Dockerfile b/test/docker/gerrit/Dockerfile new file mode 100755 index 0000000..a248447 --- /dev/null +++ b/test/docker/gerrit/Dockerfile
@@ -0,0 +1,14 @@ +FROM gerritcodereview/gerrit:3.12.0-ubuntu24 + +ARG REMOTE_GERRIT_BASE_URL +ARG HTTP_USERNAME +ARG HTTP_PASSWORD + +ENV GERRIT_SITE=/var/gerrit +RUN rm -rf "$GERRIT_SITE/plugins" && mkdir "$GERRIT_SITE/plugins" +COPY etc/ "$GERRIT_SITE/etc/" +RUN touch "$GERRIT_SITE"/.firstTimeRedirect + +COPY artifacts /tmp/ +RUN cp /tmp/remote-gerrit-account-cache.jar "$GERRIT_SITE/lib/remote-gerrit-account-cache.jar" +RUN { [ -e /tmp/gerrit.war ] && cp /tmp/gerrit.war "$GERRIT_SITE/bin/gerrit.war" ; } || true \ No newline at end of file
diff --git a/test/docker/gerrit/etc/gerrit.config b/test/docker/gerrit/etc/gerrit.config new file mode 100644 index 0000000..f604cb2 --- /dev/null +++ b/test/docker/gerrit/etc/gerrit.config
@@ -0,0 +1,34 @@ +[auth] + type = DEVELOPMENT_BECOME_ANY_ACCOUNT + +[cache] + directory = cache + +[container] + user = gerrit + +[gerrit] + basePath = git + canonicalWebUrl = http://gerrit:8080/ + installModule = com.googlesource.gerrit.plugins.remotegerritaccountcache.AccountCacheImpl$AccountCacheModule + +[httpd] + listenUrl = http://*:8080/ + +[index] + type = LUCENE + +[plugins] + allowRemoteAdmin = true + +[remote-gerrit-account-cache] + remoteGerritBaseUrl = http://remote-gerrit:8080 + httpUsername = admin + httpPassword = secret + +[sendemail] + smtpServer = localhost + enable = false + +[sshd] + listenAddress = *:29418
diff --git a/test/docker/remote-gerrit/Dockerfile b/test/docker/remote-gerrit/Dockerfile new file mode 100755 index 0000000..85bd0ca --- /dev/null +++ b/test/docker/remote-gerrit/Dockerfile
@@ -0,0 +1,9 @@ +FROM gerritcodereview/gerrit:3.12.0-ubuntu24 + +ENV GERRIT_SITE=/var/gerrit +RUN rm -rf "$GERRIT_SITE/plugins" && mkdir "$GERRIT_SITE/plugins" +COPY etc/ "$GERRIT_SITE/etc/" +RUN touch "$GERRIT_SITE"/.firstTimeRedirect + +COPY artifacts /tmp/ +RUN { [ -e /tmp/gerrit.war ] && cp /tmp/gerrit.war "$GERRIT_SITE/bin/gerrit.war" ; } || true \ No newline at end of file
diff --git a/test/docker/remote-gerrit/etc/gerrit.config b/test/docker/remote-gerrit/etc/gerrit.config new file mode 100644 index 0000000..ff0b85d --- /dev/null +++ b/test/docker/remote-gerrit/etc/gerrit.config
@@ -0,0 +1,28 @@ +[auth] + type = DEVELOPMENT_BECOME_ANY_ACCOUNT + +[cache] + directory = cache + +[container] + user = gerrit + +[gerrit] + basePath = git + canonicalWebUrl = http://gerrit:8080/ + +[httpd] + listenUrl = http://*:8080/ + +[index] + type = LUCENE + +[plugins] + allowRemoteAdmin = true + +[sendemail] + smtpServer = localhost + enable = false + +[sshd] + listenAddress = *:29418
diff --git a/test/docker/run.sh b/test/docker/run.sh new file mode 100755 index 0000000..28dacca --- /dev/null +++ b/test/docker/run.sh
@@ -0,0 +1,121 @@ +#!/usr/bin/env bash + +readlink -f / &> /dev/null || readlink() { greadlink "$@" ; } # for MacOS +MYDIR=$(dirname -- "$(readlink -f -- "$0")") +MYPROG=$(basename -- "$0") +GERRIT_ARTIFACTS=$MYDIR/gerrit/artifacts +REMOTE_GERRIT_ARTIFACTS=$MYDIR/remote-gerrit/artifacts + +die() { echo -e "\nERROR:" "$@" ; kill $$ ; exit 1 ; } # error_message + +progress() { # message cmd [args]... + local message=$1 ; shift + echo -n "$message" + "$@" & + local pid=$! + while kill -0 $pid 2> /dev/null ; do + echo -n "." + sleep 2 + done + echo + wait "$pid" +} + +usage() { # [error_message] + cat <<-EOF +Usage: + $MYPROG [--remote-gerrit-account-cache-jar|-r <FILE_PATH>] + [--gerrit-war|-g <FILE_PATH>] + + This tool runs the functional tests in a Docker environment built + from the gerritcodereview/gerrit base Docker image. + + The remote-gerrit-account-cache JAR and optionally a Gerrit WAR are + expected to be in the $GERRIT_ARTIFACTS dir; however, the + --remote-gerrit-account-cache-jar and --gerrit-war switches may be + used as helpers to specify which files to copy there. + + Options: + --help|-h + --gerrit-war|-g path to Gerrit WAR file + --remote-gerrit-account-cache-jar|-r path to remote-gerrit-account-cache JAR file + --preserve To preserve the docker setup for debugging + +EOF + + [ -n "$1" ] && echo -e "\nERROR: $1" && exit 1 + exit 0 +} + +check_prerequisite() { + docker --version > /dev/null || die "docker is not installed" + docker compose version > /dev/null || die "docker compose is not installed" +} + +build_images() { + docker compose "${COMPOSE_ARGS[@]}" build --quiet +} + +execute_tests() { + docker compose "${COMPOSE_ARGS[@]}" up --detach + local run_tests_container="$(docker compose "${COMPOSE_ARGS[@]}" ps -q run_tests | \ + xargs docker inspect --format '{{.Name}}' | sed 's|/||')" + docker cp "$MYDIR"/../../test "$run_tests_container":/ + docker compose "${COMPOSE_ARGS[@]}" exec -T --user=admin run_tests \ + '/test/docker/run_tests/start.sh' +} + +get_run_test_container() { + docker compose "${COMPOSE_ARGS[@]}" ps | grep run_tests | awk '{ print $1 }' +} + +cleanup() { + if [ "$PRESERVE" = "true" ] ; then + echo "Preserving the following docker setup" + docker compose "${COMPOSE_ARGS[@]}" ps + echo "" + echo "To exec into run_tests container, use following command:" + echo "docker exec -it $(get_run_test_container) /bin/bash" + echo "" + echo "Run the following command to bring down the setup:" + echo "docker compose" "${COMPOSE_ARGS[@]}" "down -v --rmi local" + else + docker compose "${COMPOSE_ARGS[@]}" down -v --rmi local 2>/dev/null + fi + rm -rf "$GERRIT_ARTIFACTS" "$REMOTE_GERRIT_ARTIFACTS" +} + +PRESERVE="false" +COMPOSE_ARGS=() +while (( "$#" )) ; do + case "$1" in + --help|-h) usage ;; + --gerrit-war|-g) shift ; GERRIT_WAR=$1 ;; + --remote-gerrit-account-cache-jar|-r) shift ; REMOTE_GERRIT_ACCOUNT_CACHE_JAR=$1 ;; + --preserve) PRESERVE="true" ;; + --compose-arg) shift ; COMPOSE_ARGS+=("$1") ;; + *) usage "invalid argument $1" ;; + esac + shift +done + +PROJECT_NAME="remote-gerrit-account-cache_$$" +COMPOSE_YAML="$MYDIR/docker-compose.yaml" +COMPOSE_ARGS=(--project-name "$PROJECT_NAME" -f "$COMPOSE_YAML") +check_prerequisite +mkdir -p -- "$GERRIT_ARTIFACTS" "$REMOTE_GERRIT_ARTIFACTS" + +[ -n "$REMOTE_GERRIT_ACCOUNT_CACHE_JAR" ] \ + && cp -f -- "$REMOTE_GERRIT_ACCOUNT_CACHE_JAR" "$GERRIT_ARTIFACTS/remote-gerrit-account-cache.jar" +if [ ! -e "$GERRIT_ARTIFACTS/remote-gerrit-account-cache.jar" ] ; then + MISSING="Missing $GERRIT_ARTIFACTS/remote-gerrit-account-cache.jar" + [ -n "$REMOTE_GERRIT_ACCOUNT_CACHE_JAR" ] && die "$MISSING, check for copy failure?" + usage "$MISSING, did you forget --remote-gerrit-account-cache-jar?" +fi + +[ -n "$GERRIT_WAR" ] && cp -f -- "$GERRIT_WAR" "$GERRIT_ARTIFACTS/gerrit.war" +[ -n "$GERRIT_WAR" ] && cp -f -- "$GERRIT_WAR" "$REMOTE_GERRIT_ARTIFACTS/gerrit.war" +( trap cleanup EXIT SIGTERM + progress "Building docker images" build_images + execute_tests +)
diff --git a/test/docker/run_tests/Dockerfile b/test/docker/run_tests/Dockerfile new file mode 100755 index 0000000..8dd1441 --- /dev/null +++ b/test/docker/run_tests/Dockerfile
@@ -0,0 +1,17 @@ +FROM alpine:3.16 + +ARG UID=1001 +ARG GID=1001 +ENV USER admin +ENV USER_HOME /home/$USER +ENV RUN_TESTS_DIR test/docker/run_tests + +RUN apk --update add --no-cache bash shadow curl jq + +RUN groupadd -f -g $GID users2 +RUN useradd -u $UID -g $GID $USER +RUN mkdir -p $USER_HOME +RUN chown -R $USER $USER_HOME + +USER $USER +ENTRYPOINT ["tail", "-f", "/dev/null"]
diff --git a/test/docker/run_tests/start.sh b/test/docker/run_tests/start.sh new file mode 100755 index 0000000..334d1ad --- /dev/null +++ b/test/docker/run_tests/start.sh
@@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +die() { echo "ERROR: $1" >&2 ; exit 1 ; } # error_msg + +USER_RUN_TESTS_DIR="$USER_HOME"/"$RUN_TESTS_DIR" +cp -r /test "$USER_HOME" + +"$USER_RUN_TESTS_DIR"/wait-for-it.sh "$REMOTE_GERRIT_HOST":8080 -t 60 || \ + die "Failed to start remote gerrit" + +echo "remote gerrit is up" + +"$USER_RUN_TESTS_DIR"/wait-for-it.sh "$GERRIT_HOST":8080 -t 60 || \ + die "Failed to start gerrit" + +echo "gerrit is up" + +echo "Running tests ..." + +RESULT=0 + +"$USER_RUN_TESTS_DIR"/../../test_remote_gerrit_account_cache.sh \ + --remote-gerrit "$REMOTE_GERRIT_HOST" --gerrit "$GERRIT_HOST" || RESULT=1 + +exit $RESULT
diff --git a/test/docker/run_tests/wait-for-it.sh b/test/docker/run_tests/wait-for-it.sh new file mode 100755 index 0000000..17436f2 --- /dev/null +++ b/test/docker/run_tests/wait-for-it.sh
@@ -0,0 +1,162 @@ +#!/usr/bin/env bash +# https://github.com/vishnubob/wait-for-it/blob/master/wait-for-it.sh +# Use this script to test if a given TCP host/port are available + +cmdname=$(basename -- "$0") + +echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $TIMEOUT -gt 0 ]]; then + echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" + else + echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" + fi + start_ts=$(date +%s) + while : + do + (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 + result=$? + if [[ $result -eq 0 ]]; then + end_ts=$(date +%s) + echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" + break + fi + sleep 1 + done + return $result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $QUIET -eq 1 ]]; then + timeout $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & + else + timeout $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & + fi + PID=$! + trap "kill -INT -$PID" INT + wait $PID + RESULT=$? + if [[ $RESULT -ne 0 ]]; then + echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" + fi + return $RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + hostport=(${1//:/ }) + HOST=${hostport[0]} + PORT=${hostport[1]} + shift 1 + ;; + --child) + CHILD=1 + shift 1 + ;; + -q | --quiet) + QUIET=1 + shift 1 + ;; + -s | --strict) + STRICT=1 + shift 1 + ;; + -h) + HOST="$2" + if [[ $HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + HOST="${1#*=}" + shift 1 + ;; + -p) + PORT="$2" + if [[ $PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + PORT="${1#*=}" + shift 1 + ;; + -t) + TIMEOUT="$2" + if [[ $TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + CLI="$@" + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$HOST" == "" || "$PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +TIMEOUT=${TIMEOUT:-15} +STRICT=${STRICT:-0} +CHILD=${CHILD:-0} +QUIET=${QUIET:-0} + +if [[ $CHILD -gt 0 ]]; then + wait_for + RESULT=$? + exit $RESULT +else + if [[ $TIMEOUT -gt 0 ]]; then + wait_for_wrapper + RESULT=$? + else + wait_for + RESULT=$? + fi +fi + +if [[ $CLI != "" ]]; then + if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then + echoerr "$cmdname: strict mode, refusing to execute subprocess" + exit $RESULT + fi + exec $CLI +else + exit $RESULT +fi
diff --git a/test/lib_helpers.sh b/test/lib_helpers.sh new file mode 100755 index 0000000..22a3911 --- /dev/null +++ b/test/lib_helpers.sh
@@ -0,0 +1,9 @@ +# ---- Low level execution helpers ---- + +die() { echo -e "$@" ; exit 1 ; } # error_message + +q() { "$@" > /dev/null 2>&1 ; } # cmd [args...] # quiet a command + +gcurl() { # [args...] + curl --silent --user 'admin:secret' "$@" | sed -e '1!b' -e "/^)]}'$/d" +} \ No newline at end of file
diff --git a/test/lib_result.sh b/test/lib_result.sh new file mode 100755 index 0000000..97b47c6 --- /dev/null +++ b/test/lib_result.sh
@@ -0,0 +1,38 @@ +# ---- TEST RESULTS ---- + +RESULT=0 + +result() { # test [error_message] + local result=$? + local outcome="FAIL" + if [ $result -eq 0 ] ; then + echo "PASSED - $1 test" + outcome="PASS" + else + echo "*** FAILED *** - $1 test" + RESULT=$result + [ $# -gt 1 ] && echo "$2" + fi +} + +# actual output must match expected to pass +result_out() { # test expected actual + local disp=$(echo "Expected Output:" ;\ + echo " $2" ;\ + echo "Actual Output:" ;\ + echo " $3") + + [ "$2" = "$3" ] + result "$1" "$disp" +} + +result_sortize_json() { # json + local json=$(echo "$1" | jq --indent 3 --sort-keys 2> /dev/null) + [ $? -ne 0 ] && json=$1 + echo "$json" +} + +result_out_json() { # test expected_json actual_json + local expected=$(result_sortize_json "$2") actual=$(result_sortize_json "$3") + result_out "$1" "$expected" "$actual" "Sorted JSON" +} \ No newline at end of file
diff --git a/test/test_remote_gerrit_account_cache.sh b/test/test_remote_gerrit_account_cache.sh new file mode 100755 index 0000000..7aa5550 --- /dev/null +++ b/test/test_remote_gerrit_account_cache.sh
@@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +readlink -f / &> /dev/null || readlink() { greadlink "$@" ; } # for MacOS +MYDIR=$(dirname -- "$(readlink -f -- "$0")") + +source "$MYDIR/lib_helpers.sh" +source "$MYDIR/lib_result.sh" + +HTTP_PORT=8080 +REMOTE_GERRIT_BASE_URL="http://$REMOTE_GERRIT_HOST:$HTTP_PORT" +GERRIT_BASE_URL="http://$GERRIT_HOST:$HTTP_PORT" + +q gcurl --header "Content-Type: application/json" \ + --request PUT "$REMOTE_GERRIT_BASE_URL/a/accounts/user1" \ + --data @<(cat <<EOF +{ + "email": "user1@example.com" +} +EOF +) +q gcurl --request PUT "$GERRIT_BASE_URL/a/accounts/user1" +ACTUAL=$(gcurl --request GET "$GERRIT_BASE_URL/a/accounts/user1") +EXPECTED=$(cat <<EOF +{ + "_account_id": 1000001, + "email": "user1@example.com", + "name": "user1", + "username": "user1" +} +EOF +) +result_out_json "Email added on remote gerrit site is available on the internal site" \ + "$EXPECTED" "$ACTUAL" +exit "$RESULT" \ No newline at end of file