Repository navigation
Expand file tree
/
Copy pathcoverage.sh
More file actions
executable file
·190 lines (175 loc) · 7.38 KB
/
Copy pathcoverage.sh
File metadata and controls
executable file
·190 lines (175 loc) · 7.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env bash
# Runs FlintBenchmark against a vanilla server with the JaCoCo agent attached
# and produces a line/branch coverage report of the vanilla code we care about,
# with decompiled source so missed branches can be read in place.
#
# Usage: ./coverage.sh [path/to/tests] (default: FlintBenchmark at BENCHMARK_REF)
# Env: FLINTMC flintmc binary (default: ../FlintCLI/target/release/flintmc)
# BENCHMARK_REF branch/tag/commit (default: master)
# MC_VERSION (default: 26.2)
# PORT (default: 25599)
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
MC_VERSION="${MC_VERSION:-26.2}"
JACOCO_VERSION="0.8.15"
VINEFLOWER_VERSION="1.11.1"
PORT="${PORT:-25599}"
FLINTMC="${FLINTMC:-$ROOT/../FlintCLI/target/release/flintmc}"
BENCHMARK_REPO="https://github.com/FlintTestMC/FlintBenchmark"
BENCHMARK_REF="${BENCHMARK_REF:-master}"
TESTS="${1:-}"
# Only classes in these packages (and their subpackages) are instrumented and reported.
PACKAGES=(
net.minecraft.world.level.block
net.minecraft.world.level.material
net.minecraft.world.level.redstone
net.minecraft.world.level.portal
net.minecraft.world.item
net.minecraft.world.entity
)
CACHE="$ROOT/cache"
WORK="$ROOT/work"
SERVER_DIR="$WORK/server"
OUT="$ROOT/out"
JACOCO="$CACHE/jacoco-$JACOCO_VERSION"
VINEFLOWER="$CACHE/vineflower-$VINEFLOWER_VERSION.jar"
MC="$CACHE/mc-$MC_VERSION"
log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
# --- downloads -------------------------------------------------------------
mkdir -p "$CACHE"
SERVER_JAR="$CACHE/server-$MC_VERSION.jar"
if [[ ! -f "$SERVER_JAR" ]]; then
log "Downloading vanilla server $MC_VERSION"
meta_url=$(curl -sSf https://piston-meta.mojang.com/mc/game/version_manifest_v2.json \
| jq -r --arg v "$MC_VERSION" '.versions[] | select(.id==$v) | .url')
read -r url sha1 < <(curl -sSf "$meta_url" | jq -r '.downloads.server | "\(.url) \(.sha1)"')
curl -sSfo "$SERVER_JAR.tmp" "$url"
echo "$sha1 $SERVER_JAR.tmp" | sha1sum -c --quiet
mv "$SERVER_JAR.tmp" "$SERVER_JAR"
fi
if [[ ! -d "$JACOCO" ]]; then
log "Downloading JaCoCo $JACOCO_VERSION"
curl -sSfo "$CACHE/jacoco.zip" \
"https://repo1.maven.org/maven2/org/jacoco/jacoco/$JACOCO_VERSION/jacoco-$JACOCO_VERSION.zip"
unzip -qo "$CACHE/jacoco.zip" -d "$JACOCO"
rm "$CACHE/jacoco.zip"
fi
if [[ ! -f "$VINEFLOWER" ]]; then
log "Downloading Vineflower $VINEFLOWER_VERSION"
curl -sSfo "$VINEFLOWER.tmp" \
"https://repo1.maven.org/maven2/org/vineflower/vineflower/$VINEFLOWER_VERSION/vineflower-$VINEFLOWER_VERSION.jar"
mv "$VINEFLOWER.tmp" "$VINEFLOWER"
fi
if [[ -z "$TESTS" ]]; then
BENCHMARK="$CACHE/FlintBenchmark"
[[ -d "$BENCHMARK/.git" ]] || git clone -q "$BENCHMARK_REPO" "$BENCHMARK"
git -C "$BENCHMARK" fetch -q origin "$BENCHMARK_REF"
git -C "$BENCHMARK" checkout -q --detach FETCH_HEAD
log "FlintBenchmark $BENCHMARK_REF @ $(git -C "$BENCHMARK" log -1 --format='%h %s')"
TESTS="$BENCHMARK/tests"
fi
[[ -x "$FLINTMC" ]] || { echo "flintmc not found at $FLINTMC (cargo build --release in FlintCLI)"; exit 1; }
# --- unpack, decompile and line-remap the server (cached per version + scope) ----
# The bundler jar holds the real server jar and its libraries; we launch them
# directly so our remapped classes can go first on the classpath.
INNER="$MC/server.jar"
if [[ ! -f "$INNER" ]]; then
log "Unpacking server bundle"
mkdir -p "$MC"
unzip -qo "$SERVER_JAR" 'META-INF/libraries/*' 'META-INF/libraries.list' 'META-INF/main-class' -d "$MC"
unzip -qo -j "$SERVER_JAR" "META-INF/versions/$MC_VERSION/server-$MC_VERSION.jar" -d "$MC"
mv "$MC/server-$MC_VERSION.jar" "$INNER"
fi
MAIN_CLASS=$(cat "$MC/META-INF/main-class")
LIBS=$(cut -f3 "$MC/META-INF/libraries.list" | sed "s|^|$MC/META-INF/libraries/|" | paste -sd:)
prefixes=()
for p in "${PACKAGES[@]}"; do prefixes+=("$(echo "$p" | tr . /)/"); done
SCOPE="$MC/scope-$(printf '%s\n' "${PACKAGES[@]}" | sha1sum | cut -c1-8)"
SOURCES="$SCOPE/sources"
REMAPPED="$SCOPE/classes"
if [[ ! -d "$REMAPPED" ]]; then
log "Decompiling in-scope classes with Vineflower"
rm -rf "$SCOPE" && mkdir -p "$SCOPE/scope"
unzip -qo "$INNER" "${prefixes[@]/%/*}" -d "$SCOPE/scope"
(cd "$SCOPE/scope" && zip -qr ../scope.jar .)
libs_args=()
while IFS= read -r lib; do libs_args+=("-e=$lib"); done < <(tr : '\n' <<< "$LIBS")
java -Xmx6G -jar "$VINEFLOWER" --bytecode-source-mapping=1 --dump-code-lines=1 \
--thread-count="$(nproc)" -log=WARN "-e=$INNER" "${libs_args[@]}" \
"$SCOPE/scope.jar" "$SCOPE/sources.jar" > "$SCOPE/vineflower.log" 2>&1
mkdir -p "$SOURCES" && unzip -qo "$SCOPE/sources.jar" -d "$SOURCES"
log "Remapping line numbers to the decompiled source"
java -cp "$JACOCO/lib/jacococli.jar" "$ROOT/LineRemap.java" \
"$INNER" "$SCOPE/sources.jar" "$REMAPPED.tmp" "${prefixes[@]}"
mv "$REMAPPED.tmp" "$REMAPPED"
rm -rf "$SCOPE/scope" "$SCOPE/scope.jar"
fi
# --- fresh superflat server ------------------------------------------------
log "Preparing superflat server in $SERVER_DIR"
rm -rf "$SERVER_DIR" && mkdir -p "$SERVER_DIR" "$OUT"
echo "eula=true" > "$SERVER_DIR/eula.txt"
cat > "$SERVER_DIR/server.properties" <<EOF
server-port=$PORT
online-mode=false
level-type=minecraft\:flat
generate-structures=false
spawn-protection=0
view-distance=4
simulation-distance=4
max-tick-time=-1
sync-chunk-writes=false
EOF
bot_uuid=$(python3 -c "
import hashlib, uuid
h = bytearray(hashlib.md5(b'OfflinePlayer:flintmc_testbot').digest())
h[6] = h[6] & 0x0f | 0x30; h[8] = h[8] & 0x3f | 0x80
print(uuid.UUID(bytes=bytes(h)))")
cat > "$SERVER_DIR/ops.json" <<EOF
[{"uuid": "$bot_uuid", "name": "flintmc_testbot", "level": 4, "bypassesPlayerLimit": true}]
EOF
EXEC="$OUT/jacoco.exec"
rm -f "$EXEC"
includes=$(printf '%s.*:' "${PACKAGES[@]}"); includes="${includes%:}"
# --- run -------------------------------------------------------------------
log "Starting server with JaCoCo agent"
rm -f "$WORK/stdin" && mkfifo "$WORK/stdin"
(
cd "$SERVER_DIR"
exec java -Xmx4G \
"-javaagent:$JACOCO/lib/jacocoagent.jar=destfile=$EXEC,includes=$includes,output=file" \
-cp "$REMAPPED:$INNER:$LIBS" "$MAIN_CLASS" nogui < "$WORK/stdin" > "$WORK/server.log" 2>&1
) &
SERVER_PID=$!
exec 3> "$WORK/stdin" # keep the fifo open so the server doesn't see EOF
rm "$WORK/stdin"
trap 'kill $SERVER_PID 2>/dev/null || true' EXIT
until grep -q 'Done (' "$WORK/server.log" 2>/dev/null; do
kill -0 $SERVER_PID 2>/dev/null || { cat "$WORK/server.log"; echo "server died"; exit 1; }
sleep 1
done
# Natural spawning would run mob AI that no test asked for. Tests that need a
# gamerule set it themselves.
for rule in spawn_mobs spawn_monsters spawn_patrols spawn_phantoms spawn_wandering_traders spawn_wardens; do
echo "gamerule $rule false" >&3
done
sleep 1
log "Server up, running tests from $TESTS"
set +e
"$FLINTMC" "$TESTS" -r -s "localhost:$PORT" --quiet
flint_status=$?
set -e
log "Stopping server (flushes coverage data)"
echo "stop" >&3
wait $SERVER_PID || true
exec 3>&-
trap - EXIT
# --- report ----------------------------------------------------------------
log "Generating report"
rm -rf "$OUT/html"
java -jar "$JACOCO/lib/jacococli.jar" report "$EXEC" \
--classfiles "$REMAPPED" --sourcefiles "$SOURCES" \
--name "Vanilla $MC_VERSION coverage by FlintBenchmark" \
--html "$OUT/html" --csv "$OUT/coverage.csv" --xml "$OUT/coverage.xml" --quiet
log "Report: $OUT/html/index.html"
python3 "$ROOT/summary.py" "$OUT/coverage.csv"
exit $flint_status