## BattleRunner Java subprocess — capture stdout JSON-lines output. import std/[osproc, os, strutils, streams, times, net] const DefaultRunnerJar = "/home/davide/Projects/tank-royale/runner/examples/lib/robocode-tankroyale-runner.jar" ScriptDir = currentSourcePath().parentDir() / ".." / ".." / "tools" / "battle_runner" proc runBattleRunner*(serverUrl: string, botDirs: seq[string], rounds: int, timeout: int = 30000): string = ## Starts TestBattleRunner as a subprocess. Returns captured stdout on success. ## Raises IOError on non-zero exit or TimeoutError on timeout. let jar = getEnv("TR_BATTLE_RUNNER", DefaultRunnerJar) let classDir = getEnv("TR_BATTLE_RUNNER_DIR", ScriptDir) let args = @[ "-cp", classDir & ":" & jar, "TestBattleRunner", "--server-url", serverUrl, "--rounds", $rounds, "--bot-dirs", botDirs.join(",") ] let p = startProcess("java", args = args, options = {poUsePath, poStdErrToStdOut}) defer: p.close() let deadline = epochTime() + timeout.float / 1000.0 var stdout = "" var line: string # ponytail: poll instead of blocking readLine; 50ms sleep is fine for battle timescales while epochTime() < deadline: if p.peekExitCode() != -1: # Process finished; drain remaining output without blocking while p.outputStream.atEnd == false: discard p.outputStream.readLine(line) stdout.add(line & "\n") break sleep(50) if epochTime() >= deadline and p.peekExitCode() == -1: p.terminate() raise newException(TimeoutError, "BattleRunner timed out after " & $timeout & "ms") let code = p.waitForExit() if code != 0: raise newException(IOError, "BattleRunner exited with code " & $code & "\nOutput:\n" & stdout) result = stdout when isMainModule: echo "runner_process.nim — usage:" echo " runBattleRunner(serverUrl, botDirs, rounds, timeout)" echo "" echo "Env vars:" echo " TR_BATTLE_RUNNER JAR path (default: " & DefaultRunnerJar & ")" echo " TR_BATTLE_RUNNER_DIR class dir (default: tools/battle_runner/)" echo "" echo "Cannot self-test without a running TR server. Start one first."