Improve MCC testing workflow resilience

This commit is contained in:
milutinke 2026-03-30 18:02:10 +02:00
parent 6b5435629f
commit 76e5cab248
16 changed files with 464 additions and 255 deletions

View file

@ -0,0 +1,110 @@
#!/usr/bin/env bash
sed_in_place() {
if [[ "$(uname)" == "Darwin" ]]; then
sed -i '' "$@"
else
sed -i "$@"
fi
}
ensure_java_in_path() {
if command -v java >/dev/null 2>&1 && java -version >/dev/null 2>&1; then
return 0
fi
local candidate
for candidate in \
"${JAVA_BIN:-}" \
"/opt/homebrew/opt/openjdk/bin/java" \
"/usr/local/opt/openjdk/bin/java" \
"/usr/lib/jvm/default-java/bin/java"
do
[[ -z "$candidate" ]] && continue
if [[ -x "$candidate" ]]; then
export PATH="$(dirname "$candidate"):$PATH"
export JAVA_BIN="$candidate"
if java -version >/dev/null 2>&1; then
return 0
fi
fi
done
echo "java was not found on PATH. Install Java or set JAVA_BIN." >&2
return 1
}
server_session_name() {
printf 'mc-%s\n' "${1//./_}"
}
server_running() {
local version="$1"
mc-list | grep -Fq "$(server_session_name "$version")"
}
wait_for_server_ready() {
local version="$1"
local timeout="${2:-60}"
local elapsed=0
while (( elapsed < timeout )); do
if mc-log "$version" 250 2>/dev/null | grep -Fq "Done ("; then
return 0
fi
sleep 1
((elapsed += 1))
done
echo "Timed out waiting for $version to become ready" >&2
return 1
}
wait_for_server_stop() {
local version="$1"
local timeout="${2:-60}"
local elapsed=0
while (( elapsed < timeout )); do
if ! server_running "$version"; then
return 0
fi
sleep 1
((elapsed += 1))
done
mc-kill "$version" >/dev/null 2>&1 || true
if ! server_running "$version"; then
return 0
fi
echo "Timed out waiting for $version to stop" >&2
return 1
}
disable_noisy_bots_in_ini() {
local ini_file="$1"
local section
for section in \
ScriptScheduler \
DiscordRpc \
AntiAFK \
AutoDig \
AutoAttack \
PlayerListLogger \
ReplayCapture
do
sed_in_place "/^\\[ChatBot\\.${section}\\]/,/^\\[/ { s/^Enabled = true/Enabled = false/; }" "$ini_file"
done
}
remove_stale_stdin_pipe() {
local version="$1"
local pipe_path="$MCC_SERVERS/$version/stdin.pipe"
if [[ -e "$pipe_path" && ! -p "$pipe_path" ]]; then
rm -f "$pipe_path"
fi
}

View file

@ -5,14 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
# shellcheck source=tools/mcc-env.sh
source "$REPO_ROOT/tools/mcc-env.sh"
sed_in_place() {
if [[ "$(uname)" == "Darwin" ]]; then
sed -i '' "$@"
else
sed -i "$@"
fi
}
# shellcheck source=.skills/mcc-integration-testing/scripts/common.sh
source "$SCRIPT_DIR/common.sh"
VERSION="${1:-1.21.11-Vanilla}"
SERVER_DIR="${MCC_SERVERS:?}/$VERSION"
@ -33,43 +27,6 @@ server_running() {
mc-list | grep -Fq "$SESSION_NAME"
}
wait_for_server_ready() {
local timeout="${1:-60}"
local elapsed=0
while (( elapsed < timeout )); do
if mc-log "$VERSION" 200 2>/dev/null | grep -Fq "Done ("; then
return 0
fi
sleep 1
((elapsed += 1))
done
echo "Timed out waiting for $VERSION to become ready" >&2
return 1
}
wait_for_server_stop() {
local timeout="${1:-60}"
local elapsed=0
while (( elapsed < timeout )); do
if ! server_running; then
return 0
fi
sleep 1
((elapsed += 1))
done
# Legacy servers can leave the tmux session around after stdin stop.
# Fall back to force-killing the session so the harness can continue.
mc-kill "$VERSION" >/dev/null 2>&1 || true
if ! server_running; then
return 0
fi
echo "Timed out waiting for $VERSION to stop" >&2
return 1
}
upsert_property() {
local key="$1"
local value="$2"
@ -83,14 +40,14 @@ upsert_property() {
if [[ ! -f "$PROPS_FILE" ]]; then
mc-start "$VERSION"
wait_for_server_ready
wait_for_server_ready "$VERSION"
mc-stop "$VERSION"
wait_for_server_stop
wait_for_server_stop "$VERSION"
fi
if server_running; then
mc-stop "$VERSION"
wait_for_server_stop
wait_for_server_stop "$VERSION"
fi
upsert_property "online-mode" "false"

View file

@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
# shellcheck source=tools/mcc-env.sh
source "$REPO_ROOT/tools/mcc-env.sh"
# shellcheck source=.skills/mcc-integration-testing/scripts/common.sh
source "$SCRIPT_DIR/common.sh"
usage() {
cat <<'EOF'
Usage: preflight_test_env.sh [server-dir...]
Checks the local MCC test environment and resolves common Java path issues.
EOF
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
ensure_java_in_path
command -v tmux >/dev/null 2>&1 || { echo "tmux was not found on PATH." >&2; exit 1; }
command -v dotnet >/dev/null 2>&1 || { echo "dotnet was not found on PATH." >&2; exit 1; }
command -v python3 >/dev/null 2>&1 || { echo "python3 was not found on PATH." >&2; exit 1; }
if [[ ! -d "$MCC_SERVERS" ]]; then
echo "Server root not found: $MCC_SERVERS" >&2
exit 1
fi
for server_dir in "$@"; do
[[ -z "$server_dir" ]] && continue
if [[ ! -d "$MCC_SERVERS/$server_dir" ]]; then
echo "Server directory not found: $MCC_SERVERS/$server_dir" >&2
exit 1
fi
remove_stale_stdin_pipe "$server_dir"
done
printf 'MCC_REPO=%s\n' "$MCC_REPO"
printf 'MCC_SERVERS=%s\n' "$MCC_SERVERS"
printf 'JAVA=%s\n' "$(command -v java)"
printf 'TMUX=%s\n' "$(command -v tmux)"
printf 'DOTNET=%s\n' "$(command -v dotnet)"

View file

@ -1,23 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
sed_in_place() {
if [[ "$(uname)" == "Darwin" ]]; then
sed -i '' "$@"
else
sed -i "$@"
fi
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
# shellcheck source=.skills/mcc-integration-testing/scripts/common.sh
source "$SCRIPT_DIR/common.sh"
usage() {
cat <<'EOF' >&2
Usage:
prepare_offline_mcc_config.sh <output-ini> <mc-version> [login]
prepare_offline_mcc_config.sh <template-ini> <output-ini> <mc-version> [login]
EOF
}
if [[ $# -lt 3 || $# -gt 4 ]]; then
echo "Usage: $0 <template-ini> <output-ini> <mc-version> [login]" >&2
if [[ $# -lt 2 || $# -gt 4 ]]; then
usage
exit 1
fi
TEMPLATE_INI="$1"
OUTPUT_INI="$2"
MC_VERSION="$3"
LOGIN_NAME="${4:-CursorBot}"
TEMPLATE_INI=""
OUTPUT_INI=""
MC_VERSION=""
LOGIN_NAME=""
if [[ $# -ge 3 && -f "$1" ]]; then
TEMPLATE_INI="$1"
OUTPUT_INI="$2"
MC_VERSION="$3"
LOGIN_NAME="${4:-CursorBot}"
else
OUTPUT_INI="$1"
MC_VERSION="$2"
LOGIN_NAME="${3:-CursorBot}"
fi
ACCOUNT_TYPE="${MCC_TEST_ACCOUNT_TYPE:-mojang}"
PASSWORD_VALUE="${MCC_TEST_PASSWORD-}"
@ -34,6 +51,32 @@ if [[ -z "${MCC_TEST_PASSWORD+x}" ]]; then
fi
fi
generate_template_ini() {
local template_root
template_root="$(mktemp -d "${TMPDIR:-/tmp}/mcc-config-template.XXXXXX")"
if [[ ! -f "$REPO_ROOT/MinecraftClient/bin/Release/net10.0/MinecraftClient.dll" ]]; then
dotnet build "$REPO_ROOT/MinecraftClient.sln" -c Release -v quiet --nologo >/dev/null
fi
(
cd "$template_root"
dotnet run --project "$REPO_ROOT/MinecraftClient" -c Release --no-build -- --help >/dev/null 2>&1
)
if [[ ! -f "$template_root/MinecraftClient.ini" ]]; then
echo "Failed to generate a temporary MCC config template." >&2
exit 1
fi
TEMPLATE_INI="$template_root/MinecraftClient.ini"
}
if [[ -z "$TEMPLATE_INI" ]]; then
generate_template_ini
fi
mkdir -p "$(dirname "$OUTPUT_INI")"
cp "$TEMPLATE_INI" "$OUTPUT_INI"
sed_in_place \
@ -46,6 +89,8 @@ sed_in_place \
-e 's#^AutoRespawn = false#AutoRespawn = true#' \
"$OUTPUT_INI"
disable_noisy_bots_in_ini "$OUTPUT_INI"
grep -Fq "AccountType = \"$ACCOUNT_TYPE\"" "$OUTPUT_INI" || {
echo "Failed to enforce account type $ACCOUNT_TYPE in $OUTPUT_INI" >&2
exit 1

View file

@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
# shellcheck source=tools/mcc-env.sh
source "$REPO_ROOT/tools/mcc-env.sh"
# shellcheck source=.skills/mcc-integration-testing/scripts/common.sh
source "$SCRIPT_DIR/common.sh"
usage() {
cat <<'EOF'
Usage: reset_shared_test_state.sh [--all | <server-dir>...]
Kills shared tmux test sessions and removes stale stdin pipes.
EOF
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
kill_named_session() {
local session_name="$1"
tmux kill-session -t "$session_name" 2>/dev/null || true
}
kill_named_session "mcc-debug"
if [[ $# -eq 0 || "${1:-}" == "--all" ]]; then
while IFS= read -r session_name; do
[[ -z "$session_name" ]] && continue
kill_named_session "$session_name"
done < <(tmux list-sessions 2>/dev/null | awk -F: '/^mc-/{print $1}' || true)
while IFS= read -r pipe_path; do
[[ -z "$pipe_path" ]] && continue
if [[ ! -p "$pipe_path" ]]; then
rm -f "$pipe_path"
fi
done < <(find "$MCC_SERVERS" -maxdepth 2 -name 'stdin.pipe' 2>/dev/null || true)
else
for version in "$@"; do
kill_named_session "$(server_session_name "$version")"
remove_stale_stdin_pipe "$version"
done
fi
rm -f "$MCC_REPO/mcc_input.txt"

View file

@ -64,6 +64,16 @@ run_version() {
# shellcheck disable=SC1090
source "$summary_env"
if [[ -n "${MCC_LOG:-}" && ! -f "$MCC_LOG" ]]; then
NOTE="Harness failure: MCC log was not produced."
VERDICT="❌ Fail"
fi
if [[ -n "${COMMAND_LOG:-}" && ! -f "$COMMAND_LOG" ]]; then
NOTE="Harness failure: command transcript was not produced."
VERDICT="❌ Fail"
fi
write_row "$VERSION" "$SERVER_DIR" "$PORT" "$FAMILY" "$INITIAL_STATUS" "$GRANT_STATUS" "$REVOKE_STATUS" \
"$API_STATUS" "$VERDICT" "$NOTE" "$RUN_DIR" "$MCC_LOG" "$COPIED_SERVER_LOG" "$COMMAND_LOG"
}
@ -94,6 +104,7 @@ if ! command -v tmux >/dev/null 2>&1; then
fi
if [[ "$DOTNET_OK" == "yes" ]]; then
bash "$SCRIPT_DIR/preflight_test_env.sh" >/dev/null 2>&1 || true
if ! dotnet build "$REPO_ROOT/MinecraftClient.sln" -c Release > "$BUILD_LOG" 2>&1; then
BUILD_OK="no"
fi

View file

@ -5,14 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
# shellcheck source=tools/mcc-env.sh
source "$REPO_ROOT/tools/mcc-env.sh"
sed_in_place() {
if [[ "$(uname)" == "Darwin" ]]; then
sed -i '' "$@"
else
sed -i "$@"
fi
}
# shellcheck source=.skills/mcc-integration-testing/scripts/common.sh
source "$SCRIPT_DIR/common.sh"
usage() {
cat <<'EOF'
@ -131,6 +125,7 @@ cleanup() {
fi
mc-stop "$SERVER_DIR" >/dev/null 2>&1 || true
wait_for_server_stop "$SERVER_DIR" 20 >/dev/null 2>&1 || true
ln -sfn "$RUN_DIR" "$LATEST_LINK"
write_summary
}
@ -165,43 +160,6 @@ wait_for_file_pattern() {
return 1
}
wait_for_server_ready() {
local timeout="${1:-60}"
local elapsed=0
while (( elapsed < timeout )); do
if mc-log "$SERVER_DIR" 250 2>/dev/null | grep -Fq "Done ("; then
return 0
fi
sleep 1
((elapsed += 1))
done
echo "Timed out waiting for server readiness" >&2
return 1
}
disable_noisy_bots() {
sed_in_place '/^\[ChatBot.ScriptScheduler\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
sed_in_place '/^\[ChatBot.DiscordRpc\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
sed_in_place '/^\[ChatBot.AntiAFK\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
sed_in_place '/^\[ChatBot.AutoDig\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
sed_in_place '/^\[ChatBot.AutoAttack\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
sed_in_place '/^\[ChatBot.PlayerListLogger\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
sed_in_place '/^\[ChatBot.ReplayCapture\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
}
ensure_root_config() {
if [[ -f "$REPO_ROOT/MinecraftClient.ini" ]]; then
return
fi
(
cd "$REPO_ROOT"
dotnet run --project MinecraftClient -c Release --no-build -- --help >/dev/null 2>&1
)
}
write_probe_script() {
cat > "$PROBE_SCRIPT" <<EOF
//MCCScript 1.0
@ -314,29 +272,6 @@ assert_pattern() {
grep -Fq "$pattern" "$file" || fail "$description"
}
if ! command -v java >/dev/null 2>&1 || ! java -version >/dev/null 2>&1; then
fail "java was not found on PATH."
fi
if ! command -v tmux >/dev/null 2>&1; then
fail "tmux was not found on PATH."
fi
if [[ ! -d "$MCC_SERVERS/$SERVER_DIR" ]]; then
fail "Server directory not found: $MCC_SERVERS/$SERVER_DIR"
fi
PORT="$(bash "$SCRIPT_DIR/get_server_port.sh" "$SERVER_DIR")"
ensure_root_config
"$SCRIPT_DIR/ensure_offline_server.sh" "$SERVER_DIR"
disable_noisy_bots
write_probe_script
if [[ "$PROFILE" == "legacy" && -f "$MCC_SERVERS/$SERVER_DIR/server.properties" ]]; then
sed_in_place 's/^use-native-transport=.*/use-native-transport=false/' "$MCC_SERVERS/$SERVER_DIR/server.properties"
fi
if $DO_BUILD; then
log_step "BUILD> dotnet build MinecraftClient.sln -c Release"
mcc-build > "$BUILD_LOG" 2>&1 || fail "dotnet build failed."
@ -344,17 +279,35 @@ else
: > "$BUILD_LOG"
fi
bash "$SCRIPT_DIR/preflight_test_env.sh" "$SERVER_DIR" >/dev/null || fail "Test environment preflight failed."
bash "$SCRIPT_DIR/reset_shared_test_state.sh" "$SERVER_DIR" >/dev/null || fail "Failed to reset shared test state."
if [[ ! -d "$MCC_SERVERS/$SERVER_DIR" ]]; then
fail "Server directory not found: $MCC_SERVERS/$SERVER_DIR"
fi
bash "$SCRIPT_DIR/prepare_offline_mcc_config.sh" "$CFG" "$MC_VERSION" CursorBot >/dev/null || fail "Failed to prepare temporary MCC config."
PORT="$(bash "$SCRIPT_DIR/get_server_port.sh" "$SERVER_DIR")"
"$SCRIPT_DIR/ensure_offline_server.sh" "$SERVER_DIR"
write_probe_script
if [[ "$PROFILE" == "legacy" && -f "$MCC_SERVERS/$SERVER_DIR/server.properties" ]]; then
sed_in_place 's/^use-native-transport=.*/use-native-transport=false/' "$MCC_SERVERS/$SERVER_DIR/server.properties"
fi
: > "$INPUT_FILE"
rm -f "$MCC_LOG"
log_step "Starting server $SERVER_DIR on port $PORT"
mc-start "$SERVER_DIR" >/dev/null
wait_for_server_ready || fail "Server did not become ready."
wait_for_server_ready "$SERVER_DIR" || fail "Server did not become ready."
log_step "Starting MCC for $MC_VERSION"
(
cd "$REPO_ROOT"
MCC_FILE_INPUT=1 dotnet run --project MinecraftClient -c Release --no-build -- \
"$CFG" \
CursorBot \
- \
"localhost:$PORT" \

View file

@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
# shellcheck source=tools/mcc-env.sh
source "$REPO_ROOT/tools/mcc-env.sh"
# shellcheck source=.skills/mcc-integration-testing/scripts/common.sh
source "$SCRIPT_DIR/common.sh"
VERSION="${1:-1.21.11-Vanilla}"
MC_VERSION="${VERSION%-Vanilla}"
@ -34,46 +36,12 @@ cleanup() {
fi
mc-stop "$VERSION" >/dev/null 2>&1 || true
wait_for_server_stop "$VERSION" 20 >/dev/null 2>&1 || true
}
trap cleanup EXIT
prepare_config() {
bash "$SCRIPT_DIR/prepare_offline_mcc_config.sh" "$REPO_ROOT/MinecraftClient.ini" "$CFG" "$MC_VERSION" >/dev/null
}
wait_for_file_pattern() {
local file="$1"
local pattern="$2"
local description="$3"
local timeout="${4:-60}"
local elapsed=0
while (( elapsed < timeout )); do
if [[ -f "$file" ]] && grep -Fq "$pattern" "$file"; then
return 0
fi
sleep 1
((elapsed += 1))
done
echo "Timed out waiting for: $description" >&2
return 1
}
wait_for_server_ready() {
local timeout="${1:-60}"
local elapsed=0
while (( elapsed < timeout )); do
if mc-log "$VERSION" 250 2>/dev/null | grep -Fq "Done ("; then
return 0
fi
sleep 1
((elapsed += 1))
done
echo "Timed out waiting for server readiness" >&2
return 1
bash "$SCRIPT_DIR/prepare_offline_mcc_config.sh" "$CFG" "$MC_VERSION" CursorBot >/dev/null
}
wait_for_server_log_pattern() {
@ -101,6 +69,25 @@ capture_server_logs() {
fi
}
wait_for_file_pattern() {
local file="$1"
local pattern="$2"
local description="$3"
local timeout="${4:-60}"
local elapsed=0
while (( elapsed < timeout )); do
if [[ -f "$file" ]] && grep -Fq "$pattern" "$file"; then
return 0
fi
sleep 1
((elapsed += 1))
done
echo "Timed out waiting for: $description" >&2
return 1
}
fail() {
capture_server_logs
echo "FAIL: $1" >&2
@ -146,18 +133,19 @@ run_mcc_command() {
sleep 2
}
bash "$SCRIPT_DIR/preflight_test_env.sh" "$VERSION" >/dev/null
bash "$SCRIPT_DIR/reset_shared_test_state.sh" "$VERSION" >/dev/null
"$SCRIPT_DIR/ensure_offline_server.sh" "$VERSION"
echo "Building MCC..."
mcc-build > "$BUILD_LOG" 2>&1 || fail "mcc-build failed"
prepare_config
SERVER_PORT="$(bash "$SCRIPT_DIR/get_server_port.sh" "$VERSION")"
: > "$INPUT_FILE"
echo "Building MCC..."
mcc-build > "$BUILD_LOG" 2>&1 || fail "mcc-build failed"
echo "Starting server..."
mc-start "$VERSION" >/dev/null
wait_for_server_ready || fail "Server did not become ready"
wait_for_server_ready "$VERSION" || fail "Server did not become ready"
echo "Starting MCC..."
(

View file

@ -54,4 +54,4 @@ echo "## Inferred"
echo
echo "- Only rows with real MCC and server-log artifacts count as executed proof."
echo "- Rows blocked by missing Java, tmux, or server directories are environment-limited, not product pass results."
echo "- Legacy rows remain the highest-risk bucket because static inspection suggests pre-1.12 \`Statistics\` packets may not currently reach the achievements handler."
echo "- Rows with missing MCC or command-log artifacts should be treated as harness failures until rerun confirms a product issue."