feat: add shared integration test framework for garages (#126)

Implements:
- BattleResult type and JSON-lines parser (#127)
- TR server lifecycle manager (#128)
- Bot compiler using nim c (#129)
- runBattle() orchestrator (#130)
- Example test in OscillatorBot_garage (#131)
- Framework usage guide (#132)
- TestBattleRunner.java for external server (#133)
- BattleRunner process lifecycle (#134)

Fix: runner_process.nim was redefining TimeoutError locally; now
uses std/net.TimeoutError consistently with server_manager.nim.
This commit is contained in:
2026-08-30 12:03:35 +02:00
parent a50571e6da
commit 57a1915b10
13 changed files with 654 additions and 2 deletions
+85
View File
@@ -0,0 +1,85 @@
import dev.robocode.tankroyale.runner.*;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
* Generic battle runner that connects to an external Tank Royale server and
* emits JSON-lines to stdout for downstream parsing.
*
* Usage:
* ./run_test_battle.sh --server-url ws://localhost:7654 --rounds 5 --bot-dirs /path/bot1,/path/bot2
*/
public class TestBattleRunner {
public static void main(String[] args) {
Logger.getLogger("dev.robocode.tankroyale").setLevel(Level.WARNING);
String serverUrl = null;
int rounds = 1;
List<String> botDirs = List.of();
for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case "--server-url" -> serverUrl = args[++i];
case "--rounds" -> rounds = Integer.parseInt(args[++i]);
case "--bot-dirs" -> botDirs = Arrays.asList(args[++i].split(","));
default -> { System.err.println("Unknown arg: " + args[i]); System.exit(1); }
}
}
if (serverUrl == null) { System.err.println("--server-url required"); System.exit(1); }
if (botDirs.isEmpty()) { System.err.println("--bot-dirs required"); System.exit(1); }
final String url = serverUrl;
final List<BotEntry> bots = botDirs.stream().map(BotEntry::of).collect(Collectors.toList());
final int numRounds = rounds;
final var setup = BattleSetup.classic(s -> s.setNumberOfRounds(numRounds));
try (var runner = BattleRunner.create(b -> b.externalServer(url).suppressServerOutput())) {
var owner = new Object();
try (var handle = runner.startBattleAsync(setup, bots)) {
handle.getOnGameStarted().on(owner, event -> {
var names = event.getParticipants().stream()
.map(p -> "\"" + escape(p.getName()) + "\"")
.collect(Collectors.joining(","));
System.out.println("{\"event\":\"game_started\",\"bots\":[" + names + "]}");
System.out.flush();
});
handle.getOnRoundEnded().on(owner, event -> {
var results = event.getResults().stream().map(r ->
"{\"name\":\"" + escape(r.getName()) + "\"" +
",\"score\":" + r.getTotalScore() +
",\"rank\":" + r.getRank() +
",\"survived\":" + (r.getSurvival() > 0) + "}"
).collect(Collectors.joining(","));
System.out.println("{\"event\":\"round_ended\",\"round\":" + event.getRoundNumber() + ",\"results\":[" + results + "]}");
System.out.flush();
});
var final_ = handle.awaitResults();
var results = final_.getResults().stream().map(r ->
"{\"name\":\"" + escape(r.getName()) + "\"" +
",\"totalScore\":" + r.getTotalScore() +
",\"rank\":" + r.getRank() +
",\"firstPlaces\":" + r.getFirstPlaces() +
",\"survivalCount\":" + r.getSurvival() + "}"
).collect(Collectors.joining(","));
System.out.println("{\"event\":\"battle_ended\",\"results\":[" + results + "]}");
System.out.flush();
}
} catch (Exception e) {
System.err.println("Battle failed: " + e.getMessage());
System.exit(1);
}
}
// ponytail: minimal JSON escaping — only backslash and double-quote; extend if bot names use control chars
static String escape(String s) {
return s.replace("\\", "\\\\").replace("\"", "\\\"");
}
}
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
JAR="${TANK_ROYALE_JAR:-/home/davide/Projects/tank-royale/runner/examples/lib/robocode-tankroyale-runner.jar}"
cd "$SCRIPT_DIR"
if [ ! -f TestBattleRunner.class ] || [ TestBattleRunner.java -nt TestBattleRunner.class ]; then
echo ">>> Compiling TestBattleRunner.java..." >&2
javac -cp "$JAR" TestBattleRunner.java
fi
exec java -cp ".:$JAR" TestBattleRunner "$@"