#!/usr/bin/env bash # Runs a command (normally `dotnet test`) with a throwaway mongod: a fresh data directory on a random localhost port, # stopped and deleted when the command ends. The box's own mongods (27017 shared, 27022 PrivaPub's) are never touched. # Like production's, it is a one-member replica set (rs0), so the backup's snapshot reads are what the tests exercise. # usage: tools/ci/with-test-mongod.sh dotnet test PrivaPub.sln -c Release set -euo pipefail root="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/privapub-test-mongod" mkdir -p "$root" # Leftovers of a cancelled run: anything here older than two hours. find "$root" -mindepth 1 -maxdepth 1 -type d -mmin +120 -print0 2>/dev/null | while IFS= read -r -d '' old; do [ -f "$old/pid" ] && kill "$(cat "$old/pid")" 2>/dev/null || true [ -f "$old/container" ] && podman rm -f "$(cat "$old/container")" >/dev/null 2>&1 || true rm -rf "$old" done listening() { (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null; } port="" for _ in $(seq 1 20); do candidate=$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()') case "$candidate" in 27017|27022) continue ;; esac listening "$candidate" && continue port=$candidate; break done [ -n "$port" ] || { echo "::error::no free port for the test mongod"; exit 1; } dir=$(mktemp -d "$root/run-XXXXXX") cleanup() { status=$? [ -f "$dir/pid" ] && kill "$(cat "$dir/pid")" 2>/dev/null || true [ -f "$dir/container" ] && podman rm -f "$(cat "$dir/container")" >/dev/null 2>&1 || true for _ in $(seq 1 20); do listening "$port" || break; sleep 0.5; done rm -rf "$dir" exit $status } trap cleanup EXIT INT TERM if command -v mongod >/dev/null 2>&1; then mkdir -p "$dir/db" timeout 7200 mongod --dbpath "$dir/db" --port "$port" --bind_ip 127.0.0.1 --noauth \ --wiredTigerCacheSizeGB 0.25 --replSet rs0 --quiet --logpath "$dir/mongod.log" & echo $! > "$dir/pid" how="mongod $(mongod --version | head -1)" elif command -v podman >/dev/null 2>&1; then podman run -d --rm -p "127.0.0.1:$port:27017" docker.io/library/mongo:8 --quiet --replSet rs0 > "$dir/container" how="podman mongo:8" else echo "::error::neither mongod nor podman is available for the test mongod"; exit 1 fi for _ in $(seq 1 60); do listening "$port" && break; sleep 0.5; done listening "$port" || { echo "::error::the test mongod did not start"; cat "$dir/mongod.log" 2>/dev/null | tail -20; exit 1; } # the one-member replica set, then wait until it is its primary shell() { if [ -f "$dir/container" ]; then podman exec "$(cat "$dir/container")" mongosh --quiet --eval "$1" else mongosh --quiet --port "$port" --eval "$1"; fi } member=$([ -f "$dir/container" ] && echo "127.0.0.1:27017" || echo "127.0.0.1:$port") shell "rs.initiate({_id: 'rs0', members: [{_id: 0, host: '$member'}]})" >/dev/null for _ in $(seq 1 60); do [ "$(shell 'db.hello().isWritablePrimary' 2>/dev/null)" = "true" ] && break; sleep 0.5; done [ "$(shell 'db.hello().isWritablePrimary' 2>/dev/null)" = "true" ] || { echo "::error::the test mongod never became its replica set's primary"; exit 1; } echo "test mongod: $how on 127.0.0.1:$port, replica set rs0" export PRIVAPUB_TEST_MONGOD=1 PRIVAPUB_TEST_REQUIRE_MONGOD=1 PRIVAPUB_TEST_MONGO="mongodb://127.0.0.1:$port/?directConnection=true" "$@"