Files
chunkinspector/e2e/run-servers.sh
T
2026-07-31 10:46:40 +00:00

157 lines
5.3 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Provisions a real NeoForge dedicated server, drops the built mod into it and drives it through a
# scripted session — once with the default (zero-instrumentation) configuration and once with deep
# mode on. The resulting worlds are left in build/e2e/<mode> for ChunkInspectorE2ETest to assert on.
#
# Runnable on its own: ./e2e/run-servers.sh
#
set -euo pipefail
project_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
neo_version="${NEO_VERSION:-$(sed -n 's/^neo_version=//p' "$project_dir/gradle.properties")}"
mod_jar="${MOD_JAR:-$(ls "$project_dir"/build/libs/chunkinspector-*.jar 2>/dev/null | head -1)}"
out_dir="${E2E_OUT:-$project_dir/build/e2e}"
cache_dir="${E2E_CACHE:-$HOME/.cache/chunkinspector-e2e}"
boot_timeout="${E2E_BOOT_TIMEOUT:-420}"
# The forceloaded square the assertions look for: chunks (100, 100)..(103, 103) — 4x4, far enough
# from spawn to form its own cluster and small enough to stay under the report's suspect cap.
# /forceload takes *block* coordinates, so these are the corners of those chunks: 100 * 16 = 1600
# and 103 * 16 + 15 = 1663.
force_from="1600 1600"
force_to="1663 1663"
if [[ -z "$mod_jar" || ! -f "$mod_jar" ]]; then
echo "no mod jar found; run ./gradlew jar first" >&2
exit 1
fi
log() { printf '[e2e] %s\n' "$*"; }
# --------------------------------------------------------------------- provision
installer="$cache_dir/neoforge-$neo_version-installer.jar"
template="$cache_dir/server-$neo_version"
mkdir -p "$cache_dir"
if [[ ! -f "$installer" ]]; then
log "downloading NeoForge $neo_version installer"
curl -fsSL -o "$installer.part" \
"https://maven.neoforged.net/releases/net/neoforged/neoforge/$neo_version/neoforge-$neo_version-installer.jar"
mv "$installer.part" "$installer"
fi
# The installation is cached and copied per run so the two modes can never share a world.
if [[ ! -f "$template/.installed" ]]; then
log "installing dedicated server into $template"
rm -rf "$template"
mkdir -p "$template"
# From the cache directory: the installer drops its own .log file next to the working directory.
(cd "$cache_dir" && java -jar "$installer" --installServer "$template") > "$cache_dir/install.log" 2>&1 \
|| { tail -40 "$cache_dir/install.log" >&2; exit 1; }
touch "$template/.installed"
fi
args_file="libraries/net/neoforged/neoforge/$neo_version/unix_args.txt"
[[ -f "$template/$args_file" ]] || { echo "missing $args_file in the installed server" >&2; exit 1; }
# ------------------------------------------------------------------------- run
# run <mode> <extra jvm properties...>
run() {
local mode="$1"
shift
local dir="$out_dir/$mode"
log "preparing $mode server in $dir"
rm -rf "$dir"
mkdir -p "$dir"
cp -a "$template/." "$dir/"
rm -f "$dir/.installed"
mkdir -p "$dir/mods"
cp "$mod_jar" "$dir/mods/"
echo "eula=true" > "$dir/eula.txt"
cat > "$dir/server.properties" <<'PROPERTIES'
level-name=world
level-type=minecraft:flat
level-seed=e2e
online-mode=false
max-players=1
view-distance=10
simulation-distance=10
spawn-protection=0
sync-chunk-writes=false
enable-jmx-monitoring=false
enable-status=false
max-tick-time=-1
PROPERTIES
local fifo="$dir/console-in"
mkfifo "$fifo"
(
cd "$dir"
exec java -Xmx2G "$@" "@$args_file" --nogui < console-in > console.log 2>&1
) &
local server=$!
# Opening the write end unblocks the server's read end; keeping it open keeps stdin alive.
exec 9> "$fifo"
log "waiting for $mode server to finish loading"
local waited=0
until grep -q 'Done (' "$dir/console.log" 2>/dev/null; do
if ! kill -0 "$server" 2>/dev/null; then
exec 9>&-
tail -40 "$dir/console.log" >&2
echo "$mode server exited before it finished loading" >&2
exit 1
fi
sleep 1
waited=$((waited + 1))
if [[ $waited -ge $boot_timeout ]]; then
exec 9>&-
kill "$server" 2>/dev/null || true
echo "$mode server did not start within ${boot_timeout}s" >&2
exit 1
fi
done
say() { printf '%s\n' "$1" >&9; log " > $1"; sleep "${2:-2}"; }
# A leaking chunk loader, simulated with the one permanent ticket every vanilla server can make.
# The pause covers generating the 4x4 square, which has never been visited before.
say "forceload add $force_from $force_to" 5
say "chunkinspector status"
say "chunkinspector why 102 102"
say "chunkinspector snapshot full" 5
# Long enough for at least one periodic snapshot (interval is 200 ticks = 10s) to also land.
say "chunkinspector status" 12
say "stop" 0
log "waiting for $mode server to shut down"
wait "$server" || true
exec 9>&-
rm -f "$fifo"
local summaries="$dir/world/chunk-summaries"
[[ -d "$summaries" ]] || { tail -60 "$dir/console.log" >&2; echo "no $summaries produced" >&2; exit 1; }
log "$mode done: $(ls "$summaries" | wc -l) files in chunk-summaries"
}
mkdir -p "$out_dir"
# Tier one: nothing injected into the game at all.
run light \
-Dchunkinspector.interval=200 \
-Dchunkinspector.retention=5
# Tier two: the opt-in expensive analysis.
run deep \
-Dchunkinspector.deep=true \
-Dchunkinspector.attribution=true \
-Dchunkinspector.interval=200 \
-Dchunkinspector.retention=5
log "all servers completed"