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