2026-03-22 21:19:55 +00:00
#!/usr/bin/env bash
2026-03-21 21:02:13 +01:00
set -euo pipefail
2026-03-22 21:19:55 +00:00
SCRIPT_DIR = " $( cd " $( dirname " $0 " ) " && pwd ) "
2026-03-21 21:02:13 +01:00
REPO_ROOT = " $( cd " $SCRIPT_DIR /../../.. " && pwd ) "
2026-03-22 21:19:55 +00:00
# shellcheck source=tools/mcc-env.sh
source " $REPO_ROOT /tools/mcc-env.sh "
2026-03-30 18:02:10 +02:00
# shellcheck source=.skills/mcc-integration-testing/scripts/common.sh
source " $SCRIPT_DIR /common.sh "
2026-03-22 21:19:55 +00:00
2026-03-21 21:02:13 +01:00
VERSION = " ${ 1 :- 1 .21.11-Vanilla } "
2026-03-23 15:35:34 +01:00
MC_VERSION = " ${ VERSION %-Vanilla } "
if [ [ " $MC_VERSION " = = " $VERSION " ] ] ; then
MC_VERSION = " $VERSION "
fi
2026-03-21 21:02:13 +01:00
RUN_ROOT = " ${ TMPDIR :- /tmp } /mcc-integration-testing "
RUN_ID = " $( date +%Y%m%d-%H%M%S) "
RUN_DIR = " $RUN_ROOT / $RUN_ID "
SERVER_LOG_FILE = " $MCC_SERVERS / $VERSION /logs/latest.log "
MCC_LOG = " $RUN_DIR /mcc.log "
BUILD_LOG = " $RUN_DIR /build.log "
SERVER_TMUX_LOG = " $RUN_DIR /server-tmux.log "
SERVER_FILE_LOG = " $RUN_DIR /server-latest.log "
INPUT_FILE = " $REPO_ROOT /mcc_input.txt "
2026-03-23 15:35:34 +01:00
CFG = " $RUN_DIR /MinecraftClient. $MC_VERSION .ini "
2026-03-21 21:02:13 +01:00
MCC_PID = ""
mkdir -p " $RUN_DIR "
cleanup( ) {
if [ [ -n " ${ MCC_PID :- } " ] ] && kill -0 " $MCC_PID " 2>/dev/null; then
mcc-cmd "quit" >/dev/null 2>& 1 || true
sleep 2
kill " $MCC_PID " 2>/dev/null || true
wait " $MCC_PID " 2>/dev/null || true
fi
mc-stop " $VERSION " >/dev/null 2>& 1 || true
2026-03-30 18:02:10 +02:00
wait_for_server_stop " $VERSION " 20 >/dev/null 2>& 1 || true
2026-03-21 21:02:13 +01:00
}
trap cleanup EXIT
2026-03-23 15:35:34 +01:00
prepare_config( ) {
2026-03-30 18:02:10 +02:00
bash " $SCRIPT_DIR /prepare_offline_mcc_config.sh " " $CFG " " $MC_VERSION " CursorBot >/dev/null
2026-03-23 15:35:34 +01:00
}
2026-03-30 18:02:10 +02:00
wait_for_server_log_pattern( ) {
local pattern = " $1 "
local description = " $2 "
local timeout = " ${ 3 :- 60 } "
2026-03-21 21:02:13 +01:00
local elapsed = 0
while ( ( elapsed < timeout ) ) ; do
2026-03-30 18:02:10 +02:00
if [ [ -f " $SERVER_LOG_FILE " ] ] && grep -Fq " $pattern " " $SERVER_LOG_FILE " ; then
2026-03-21 21:02:13 +01:00
return 0
fi
sleep 1
( ( elapsed += 1) )
done
2026-03-30 18:02:10 +02:00
echo " Timed out waiting for server log: $description " >& 2
2026-03-21 21:02:13 +01:00
return 1
}
2026-03-30 18:02:10 +02:00
capture_server_logs( ) {
mc-log " $VERSION " 400 > " $SERVER_TMUX_LOG " 2>/dev/null || true
if [ [ -f " $SERVER_LOG_FILE " ] ] ; then
cp " $SERVER_LOG_FILE " " $SERVER_FILE_LOG "
fi
2026-03-21 21:02:13 +01:00
}
2026-03-30 18:02:10 +02:00
wait_for_file_pattern( ) {
local file = " $1 "
local pattern = " $2 "
local description = " $3 "
local timeout = " ${ 4 :- 60 } "
2026-03-21 21:02:13 +01:00
local elapsed = 0
while ( ( elapsed < timeout ) ) ; do
2026-03-30 18:02:10 +02:00
if [ [ -f " $file " ] ] && grep -Fq " $pattern " " $file " ; then
2026-03-21 21:02:13 +01:00
return 0
fi
sleep 1
( ( elapsed += 1) )
done
2026-03-30 18:02:10 +02:00
echo " Timed out waiting for: $description " >& 2
2026-03-21 21:02:13 +01:00
return 1
}
fail( ) {
capture_server_logs
echo " FAIL: $1 " >& 2
echo " Run directory: $RUN_DIR " >& 2
exit 1
}
assert_contains( ) {
local file = " $1 "
local pattern = " $2 "
local description = " $3 "
grep -Fq " $pattern " " $file " || fail " $description "
}
assert_not_contains( ) {
local file = " $1 "
local pattern = " $2 "
local description = " $3 "
if grep -Fq " $pattern " " $file " ; then
fail " $description "
fi
}
run_server_command( ) {
local cmd = " $1 "
2026-03-23 15:35:34 +01:00
local attempt
2026-03-21 21:02:13 +01:00
echo " SERVER> $cmd "
2026-03-23 15:35:34 +01:00
for attempt in 1 2 3 4 5; do
if mc-rcon " $cmd " >/dev/null 2>& 1; then
return 0
fi
sleep 1
done
fail " Server command failed: $cmd "
2026-03-21 21:02:13 +01:00
}
run_mcc_command( ) {
local cmd = " $1 "
echo " MCC> $cmd "
mcc-cmd " $cmd "
sleep 2
}
2026-03-30 18:02:10 +02:00
bash " $SCRIPT_DIR /preflight_test_env.sh " " $VERSION " >/dev/null
bash " $SCRIPT_DIR /reset_shared_test_state.sh " " $VERSION " >/dev/null
2026-03-21 21:02:13 +01:00
" $SCRIPT_DIR /ensure_offline_server.sh " " $VERSION "
2026-03-30 18:02:10 +02:00
echo "Building MCC..."
mcc-build > " $BUILD_LOG " 2>& 1 || fail "mcc-build failed"
2026-03-23 15:35:34 +01:00
prepare_config
2026-03-24 17:50:04 +01:00
SERVER_PORT = " $( bash " $SCRIPT_DIR /get_server_port.sh " " $VERSION " ) "
2026-03-21 21:02:13 +01:00
: > " $INPUT_FILE "
echo "Starting server..."
mc-start " $VERSION " >/dev/null
2026-03-30 18:02:10 +02:00
wait_for_server_ready " $VERSION " || fail "Server did not become ready"
2026-03-21 21:02:13 +01:00
echo "Starting MCC..."
2026-03-23 15:35:34 +01:00
(
cd " $REPO_ROOT "
2026-03-24 17:50:04 +01:00
MCC_FILE_INPUT = 1 dotnet run --project MinecraftClient -c Release --no-build -- " $CFG " CursorBot - " localhost: $SERVER_PORT " > " $MCC_LOG " 2>& 1
2026-03-23 15:35:34 +01:00
) &
2026-03-21 21:02:13 +01:00
MCC_PID = $!
wait_for_file_pattern " $MCC_LOG " "Server was successfully joined." "MCC join success" 90 || fail "MCC failed to join"
wait_for_server_log_pattern "CursorBot joined the game" "server join entry" 30 || fail "Server never logged the join"
run_server_command "op CursorBot"
run_server_command "gamerule sendCommandFeedback true"
run_server_command "gamerule logAdminCommands true"
run_server_command "time set day"
run_server_command "weather clear"
sleep 2
2026-03-23 00:41:40 +00:00
# ── Phase 1: Basic status and info commands ──
2026-03-21 21:02:13 +01:00
run_mcc_command "health"
run_mcc_command "list"
run_mcc_command "inventory player list"
run_mcc_command "/gamemode creative"
run_mcc_command "inventory creativegive 36 Diamond 16"
run_mcc_command "inventory player list"
run_mcc_command "entity"
run_mcc_command "/time query daytime"
run_mcc_command "smoke_test_from_mcc_full_spectrum"
2026-03-23 00:41:40 +00:00
# ── Phase 2: Movement and look commands ──
run_mcc_command "/tp CursorBot 0 -60 0"
sleep 3
run_mcc_command "look up"
sleep 1
run_mcc_command "look down"
sleep 1
run_mcc_command "look east"
sleep 1
# ── Phase 3: Advanced inventory operations ──
run_mcc_command "inventory creativegive 37 IronSword 1"
run_mcc_command "inventory creativegive 38 GoldenApple 8"
run_mcc_command "inventory player list"
run_mcc_command "inventory creativeclear 38"
run_mcc_command "inventory player list"
# ── Phase 4: Block placement and interaction ──
run_server_command "execute as CursorBot at @s run fill ~1 ~ ~1 ~3 ~2 ~3 minecraft:stone"
sleep 2
run_server_command "execute as CursorBot at @s run setblock ~5 ~ ~5 minecraft:chest"
sleep 1
run_server_command "execute as CursorBot at @s run setblock ~5 ~1 ~5 minecraft:furnace"
sleep 1
run_server_command "execute as CursorBot at @s run setblock ~6 ~ ~5 minecraft:crafting_table"
sleep 1
# ── Phase 5: Entity spawning (expanded coverage) ──
2026-03-21 21:02:13 +01:00
run_server_command "execute as CursorBot at @s run summon minecraft:cow ~2 ~ ~"
run_server_command "execute as CursorBot at @s run summon minecraft:zombie ~4 ~ ~"
run_server_command "execute as CursorBot at @s run summon minecraft:creeper ~6 ~ ~"
run_server_command "execute as CursorBot at @s run summon minecraft:skeleton ~8 ~ ~"
run_server_command "execute as CursorBot at @s run summon minecraft:villager ~-2 ~ ~"
run_server_command "execute as CursorBot at @s run summon minecraft:allay ~-4 ~ ~"
run_server_command "execute as CursorBot at @s run summon minecraft:armor_stand ~ ~ ~2"
2026-03-23 00:41:40 +00:00
run_server_command "execute as CursorBot at @s run summon minecraft:item_display ~-6 ~ ~ {item:{id:\"minecraft:diamond\",count:1}}"
run_server_command "execute as CursorBot at @s run summon minecraft:spider ~10 ~ ~"
run_server_command "execute as CursorBot at @s run summon minecraft:pig ~-8 ~ ~"
2026-03-21 21:02:13 +01:00
sleep 2
run_mcc_command "entity"
2026-03-23 00:41:40 +00:00
# ── Phase 6: Effects and environment ──
run_server_command "effect give CursorBot minecraft:speed 30 1"
sleep 2
run_mcc_command "health"
run_server_command "effect give CursorBot minecraft:regeneration 10 1"
sleep 2
run_mcc_command "health"
# ── Phase 7: Gamemode cycling ──
run_mcc_command "/gamemode survival"
sleep 2
run_mcc_command "health"
run_mcc_command "/gamemode creative"
sleep 2
# ── Phase 8: Dimension change (nether) ──
run_server_command "execute in minecraft:the_nether run tp CursorBot 0 64 0"
sleep 4
run_mcc_command "health"
run_server_command "execute in minecraft:overworld run tp CursorBot 0 -60 0"
sleep 4
# ── Phase 9: Server chat and whisper ──
run_server_command "say Hello from the server console"
sleep 2
run_server_command "msg CursorBot This is a private whisper"
sleep 2
run_mcc_command "integration_test_chat_response"
# ── Phase 10: Particles, sounds, and explosions ──
2026-03-21 21:02:13 +01:00
run_server_command "execute as CursorBot at @s run particle minecraft:happy_villager ~ ~1 ~ 0.5 0.5 0.5 0 12 force"
run_server_command "execute as CursorBot at @s run particle minecraft:end_rod ~ ~1 ~ 0.5 0.5 0.5 0.01 20 force"
run_server_command "execute as CursorBot at @s run particle minecraft:explosion ~ ~1 ~ 0 0 0 0 1 force"
run_server_command "execute as CursorBot at @s run particle minecraft:totem_of_undying ~ ~1 ~ 0.5 0.5 0.5 0.1 20 force"
2026-03-23 00:41:40 +00:00
run_server_command "execute as CursorBot at @s run particle minecraft:flame ~ ~1 ~ 0.2 0.2 0.2 0.02 30 force"
run_server_command "execute as CursorBot at @s run particle minecraft:heart ~ ~2 ~ 0.3 0.3 0.3 0 5 force"
2026-03-21 21:02:13 +01:00
run_server_command "execute as CursorBot at @s run playsound minecraft:entity.lightning_bolt.thunder master CursorBot ~ ~ ~ 1 1 0"
run_server_command "execute as CursorBot at @s run playsound minecraft:block.note_block.bell master CursorBot ~ ~ ~ 1 1 0"
2026-03-23 00:41:40 +00:00
run_server_command "execute as CursorBot at @s run playsound minecraft:entity.experience_orb.pickup master CursorBot ~ ~ ~ 1 1 0"
2026-03-21 21:02:13 +01:00
run_server_command "execute as CursorBot at @s run summon minecraft:tnt ~3 ~ ~"
sleep 2
run_server_command "execute as CursorBot at @s run summon minecraft:tnt ~6 ~ ~"
2026-03-23 00:41:40 +00:00
# ── Phase 11: Kill and respawn cycle ──
run_mcc_command "/gamemode survival"
sleep 2
run_server_command "kill CursorBot"
sleep 4
run_mcc_command "respawn"
sleep 4
run_mcc_command "health"
run_mcc_command "/gamemode creative"
sleep 2
2026-03-21 21:02:13 +01:00
sleep 6
capture_server_logs
2026-03-23 00:41:40 +00:00
# ── Assertions: MCC log ──
2026-03-21 21:02:13 +01:00
assert_contains " $MCC_LOG " "Server was successfully joined." "MCC never joined the server"
assert_contains " $MCC_LOG " "[FileInput] > inventory player list" "Inventory command was not executed"
assert_contains " $MCC_LOG " "[FileInput] > entity" "Entity command was not executed"
assert_contains " $MCC_LOG " "[FileInput] > /gamemode creative" "Creative mode command was not executed from MCC"
assert_contains " $MCC_LOG " "Requested Diamond x16 in slot #36" "Creative inventory give did not succeed"
assert_contains " $MCC_LOG " "smoke_test_from_mcc_full_spectrum" "Client-originated chat was not observed"
2026-03-23 00:41:40 +00:00
assert_contains " $MCC_LOG " "[FileInput] > look up" "Look command was not executed"
assert_contains " $MCC_LOG " "[FileInput] > /gamemode survival" "Survival mode switch was not executed"
assert_contains " $MCC_LOG " "[FileInput] > respawn" "Respawn command was not executed"
assert_contains " $MCC_LOG " "[FileInput] > health" "Health command was not executed"
assert_contains " $MCC_LOG " "integration_test_chat_response" "Chat response test message was not observed"
2026-03-21 21:02:13 +01:00
assert_not_contains " $MCC_LOG " "Please enable InventoryHandling" "Inventory handling is still disabled"
assert_not_contains " $MCC_LOG " "Please enable EntityHandling" "Entity handling is still disabled"
assert_not_contains " $MCC_LOG " "You must be in Creative gamemode" "Creative mode was not active when creativegive ran"
assert_not_contains " $MCC_LOG " "Failed to load settings" "MCC failed to reload its config"
2026-03-23 00:41:40 +00:00
assert_not_contains " $MCC_LOG " "NullReferenceException" "A NullReferenceException occurred during the test"
2026-03-21 21:02:13 +01:00
2026-03-23 00:41:40 +00:00
# ── Assertions: Server log ──
2026-03-21 21:02:13 +01:00
assert_contains " $SERVER_FILE_LOG " "CursorBot joined the game" "Server never saw CursorBot join"
assert_contains " $SERVER_FILE_LOG " "smoke_test_from_mcc_full_spectrum" "Server never received the client chat message"
assert_contains " $SERVER_FILE_LOG " "Displaying particle minecraft:happy_villager" "Particle events were not recorded on the server"
assert_contains " $SERVER_FILE_LOG " "Played sound minecraft:block.note_block.bell to CursorBot" "Sound events were not recorded on the server"
assert_contains " $SERVER_FILE_LOG " "Summoned new Primed TNT" "TNT summon did not occur on the server"
2026-03-23 00:41:40 +00:00
assert_contains " $SERVER_FILE_LOG " "integration_test_chat_response" "Server never received the chat response test message"
assert_contains " $SERVER_FILE_LOG " "Hello from the server console" "Server say command was not logged"
assert_contains " $SERVER_FILE_LOG " "Killed CursorBot" "Server kill command did not execute"
2026-03-21 21:02:13 +01:00
assert_not_contains " $SERVER_FILE_LOG " "Sending unknown packet 'clientbound/minecraft:disconnect'" "Server hit the disconnect packet regression during the test"
cat <<EOF
PASS
Run directory: $RUN_DIR
MCC log: $MCC_LOG
Server log: $SERVER_FILE_LOG
Build log: $BUILD_LOG
EOF