291 lines
7.4 KiB
Rust
291 lines
7.4 KiB
Rust
use std::{
|
|
env,
|
|
net::TcpStream,
|
|
path::{Path, PathBuf},
|
|
process::{Child, Command},
|
|
thread::sleep,
|
|
time::Duration,
|
|
};
|
|
|
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
|
|
|
type DynError = Box<dyn std::error::Error>;
|
|
|
|
fn main() {
|
|
if let Err(e) = try_main() {
|
|
eprintln!("{e}");
|
|
std::process::exit(-1)
|
|
}
|
|
}
|
|
|
|
fn try_main() -> Result<(), DynError> {
|
|
let task = env::args().nth(1);
|
|
match task.as_deref() {
|
|
Some("local") => local()?,
|
|
Some("local2") => local2()?,
|
|
Some("vs_production" | "vs_prod") => vs_production()?,
|
|
Some("regression") => regression()?,
|
|
Some("docker") => docker()?,
|
|
_ => print_help(),
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn print_help() {
|
|
eprintln!(
|
|
"Tasks:
|
|
|
|
local runs the snake on a local game
|
|
local2 runs the snake twice on a local game
|
|
vs_production | vs_prod runs the snake against the active server
|
|
regression runs the snake against the active server multiple times
|
|
|
|
docker package docker image and upload to docker.mkaenner.de/snake
|
|
"
|
|
)
|
|
}
|
|
|
|
fn local() -> Result<(), DynError> {
|
|
let mut snake = run_snake(8000, None)?;
|
|
|
|
let game = Command::new("./battlesnake-cli")
|
|
.current_dir(project_root())
|
|
.args([
|
|
"play",
|
|
"-W",
|
|
"11",
|
|
"-H",
|
|
"11",
|
|
"--name",
|
|
"local test",
|
|
"--url",
|
|
"http://localhost:8000",
|
|
"-g",
|
|
"solo",
|
|
"--browser",
|
|
])
|
|
.status();
|
|
|
|
game.and(snake.kill())?;
|
|
Ok(())
|
|
}
|
|
|
|
fn local2() -> Result<(), DynError> {
|
|
let mut snake = run_snake(8000, None)?;
|
|
|
|
let game = Command::new("./battlesnake-cli")
|
|
.current_dir(project_root())
|
|
.args([
|
|
"play",
|
|
"-W",
|
|
"11",
|
|
"-H",
|
|
"11",
|
|
"--name",
|
|
"local test1",
|
|
"--url",
|
|
"http://localhost:8000",
|
|
"--name",
|
|
"local test2",
|
|
"--url",
|
|
"http://localhost:8000",
|
|
"-g",
|
|
"duel",
|
|
"--browser",
|
|
])
|
|
.status();
|
|
|
|
game.and(snake.kill())?;
|
|
Ok(())
|
|
}
|
|
|
|
fn vs_production() -> Result<(), DynError> {
|
|
let mut snake = run_snake(8000, None)?;
|
|
|
|
let game = Command::new("./battlesnake-cli")
|
|
.current_dir(project_root())
|
|
.args([
|
|
"play",
|
|
"-W",
|
|
"11",
|
|
"-H",
|
|
"11",
|
|
"--name",
|
|
"local",
|
|
"--url",
|
|
"http://localhost:8000",
|
|
"--name",
|
|
"production",
|
|
"--url",
|
|
"https://snake.mkaenner.de",
|
|
"-g",
|
|
"duel",
|
|
"--browser",
|
|
])
|
|
.output();
|
|
|
|
let game = snake.kill().and(game)?;
|
|
if !game.status.success() {
|
|
eprintln!("game output: {}", String::from_utf8(game.stderr)?);
|
|
eprintln!("game status: {}", game.status);
|
|
Err("failed to play the game".into())
|
|
} else {
|
|
const WIN_PHRASE: &[u8] = b"local was the winner.\n";
|
|
let won =
|
|
&game.stderr[(game.stderr.len() - WIN_PHRASE.len())..game.stderr.len()] == WIN_PHRASE;
|
|
if won {
|
|
println!("The local snake has won");
|
|
} else {
|
|
println!("The production snake has won");
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn regression() -> Result<(), DynError> {
|
|
let mut snake = run_snake(8000, Some("error"))?;
|
|
let mut prod = match run_production(8001) {
|
|
Ok(prod) => prod,
|
|
err => {
|
|
snake.kill()?;
|
|
err?;
|
|
unreachable!()
|
|
}
|
|
};
|
|
|
|
let res = try_regression();
|
|
|
|
snake.kill().and(prod.kill())?;
|
|
let (won, games) = res?;
|
|
println!(
|
|
"\nThe local snake has won {won}/{games} games ({}%)",
|
|
won * 100 / games
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn try_regression() -> Result<(usize, usize), DynError> {
|
|
const GAMES: usize = 100;
|
|
// limit the parallelism
|
|
rayon::ThreadPoolBuilder::new()
|
|
.num_threads(std::thread::available_parallelism()?.get() / 3)
|
|
.build_global()
|
|
.unwrap();
|
|
|
|
let won_count = (0..GAMES)
|
|
.into_par_iter()
|
|
.map(|_| -> Option<usize> {
|
|
eprint!(".");
|
|
let game = Command::new("./battlesnake-cli")
|
|
.current_dir(project_root())
|
|
.args([
|
|
"play",
|
|
"-W",
|
|
"11",
|
|
"-H",
|
|
"11",
|
|
"--name",
|
|
"local",
|
|
"--url",
|
|
"http://localhost:8000",
|
|
"--name",
|
|
"production",
|
|
"--url",
|
|
"http://localhost:8001",
|
|
"-g",
|
|
"duel",
|
|
])
|
|
.output()
|
|
.ok()?;
|
|
if !game.status.success() {
|
|
eprintln!("game output: {}", String::from_utf8(game.stderr).ok()?);
|
|
eprintln!("game status: {}", game.status);
|
|
None
|
|
} else {
|
|
const WIN_PHRASE: &[u8] = b"local was the winner.\n";
|
|
let won = &game.stderr[(game.stderr.len() - WIN_PHRASE.len())..game.stderr.len()]
|
|
== WIN_PHRASE;
|
|
Some(usize::from(won))
|
|
}
|
|
})
|
|
.flatten()
|
|
.sum();
|
|
|
|
Ok((won_count, GAMES))
|
|
}
|
|
|
|
fn run_snake(port: u16, log: Option<&str>) -> Result<Child, DynError> {
|
|
let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
|
|
let mut snake = Command::new(cargo);
|
|
snake
|
|
.current_dir(project_root())
|
|
.env("PORT", port.to_string());
|
|
if let Some(log) = log {
|
|
snake.env("RUST_LOG", log);
|
|
}
|
|
let mut snake = snake
|
|
.args(
|
|
["run", "--bin", "battlesnake"]
|
|
.map(str::to_string)
|
|
.into_iter()
|
|
.chain(env::args().skip(2)),
|
|
)
|
|
.spawn()?;
|
|
loop {
|
|
if let Some(status) = snake.try_wait()? {
|
|
Err(format!("{status}"))?;
|
|
}
|
|
if TcpStream::connect(("127.0.0.1", port)).is_ok() {
|
|
break Ok(snake);
|
|
}
|
|
sleep(Duration::from_secs(1));
|
|
}
|
|
}
|
|
|
|
fn run_production(port: u16) -> Result<Child, DynError> {
|
|
let mut snake = Command::new("docker")
|
|
.args([
|
|
"run",
|
|
"--env",
|
|
"RUST_LOG=error",
|
|
"--env",
|
|
format!("ROCKET_PORT={}", port).as_str(),
|
|
"--network=host",
|
|
"-i",
|
|
"docker.mkaenner.de/snake:latest",
|
|
])
|
|
.spawn()?;
|
|
loop {
|
|
if let Some(status) = snake.try_wait()? {
|
|
Err(format!("{status}"))?;
|
|
}
|
|
if TcpStream::connect(("127.0.0.1", port)).is_ok() {
|
|
break Ok(snake);
|
|
}
|
|
sleep(Duration::from_secs(1));
|
|
}
|
|
}
|
|
|
|
fn docker() -> Result<(), DynError> {
|
|
if !Command::new("docker")
|
|
.current_dir(project_root())
|
|
.args(["build", ".", "-t", "docker.mkaenner.de/snake"])
|
|
.status()?
|
|
.success()
|
|
{
|
|
Err("Build of docker image failed")?;
|
|
}
|
|
Command::new("docker")
|
|
.args(["push", "docker.mkaenner.de/snake"])
|
|
.status()?;
|
|
Ok(())
|
|
}
|
|
|
|
fn project_root() -> PathBuf {
|
|
Path::new(&env!("CARGO_MANIFEST_DIR"))
|
|
.ancestors()
|
|
.nth(1)
|
|
.unwrap()
|
|
.to_path_buf()
|
|
}
|