157 lines
5.3 KiB
Bash
Executable File
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"
|