Rust script to iterate over all scenarios with openclonk-server and look for script errors

install-platforms
Julius Michaelis 2018-01-12 23:15:29 +01:00
parent 54e5c6de38
commit 24cf662cbd
1 changed files with 195 additions and 0 deletions

View File

@ -0,0 +1,195 @@
#!/usr/bin/env run-cargo-script
//! ```cargo
//! [dependencies]
//! time = "0.1.25"
//! walkdir = "1"
//! clap = "~2.26"
//! regex = "0.2"
//! wait-timeout = "0.1.5"
//! ```
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2018, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
extern crate walkdir;
extern crate regex;
extern crate wait_timeout;
#[macro_use]
extern crate clap;
use std::path::Path;
use std::error::Error;
use std::io::prelude::*;
use std::process::{Command, Stdio};
use walkdir::WalkDir;
//use clap::clap_app;
use std::thread;
use std::sync::mpsc;
use std::io::BufReader;
use std::time::{Instant, Duration};
use regex::Regex;
use wait_timeout::ChildExt;
fn main() {
let matches = clap_app!(myapp =>
(version: "0.3")
(author: "Julius Michaelis <jcoc@liftm.de>")
(about: "Test-run all scenarios, grep for script errors")
(@arg planet: -p --planet +takes_value "Path to planet directory containing game data")
(@arg openclonk: -e --openclonk +takes_value "Path to headless executable")
(@arg verbose: -v --verbose ... "Verbosity")
).get_matches();
let oc = matches.value_of("openclonk").unwrap_or("./openclonk-server");
let planet = matches.value_of("planet").unwrap_or("./planet");
let verbosity = matches.occurrences_of("verbose");
for entry in WalkDir::new(planet).into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.file_name().to_str()
.map_or(false, |s| s.ends_with(".ocs")))
{
let path = entry.path();
test(path, oc, verbosity);
}
}
fn test(scen : &Path, oc : &str, verbosity : u64) {
if verbosity >= 1 {
println!("Testing {}", scen.display());
}
let start = Instant::now();
let test_dur = Duration::from_secs(450);
let shutdown_to = Duration::from_secs(15);
let test_deadline = start + test_dur - shutdown_to;
// [07:45:04] C4AulScriptEngine linked - 86722 lines, 0 warnings, 0 errors
let status_re = Regex::new(r"^\[\d\d:\d\d:\d\d\] C4AulScriptEngine linked - (.*)\n$").unwrap();
// [07:45:08] Game started.
let load_done_re = Regex::new(r"^\[\d\d:\d\d:\d\d\] (Game started|Game cleared).\n$").unwrap();
let mut process = match Command::new(oc)
.arg("--language=US")
.arg(scen)
.arg("Test.ocp")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
{
Err(why) => panic!("couldn't spawn oc: {}", why.description()),
Ok(process) => process,
};
let ocout = cont_read(process.stdout.take().unwrap(), verbosity >= 3);
// Look for error counts
loop {
match ocout.recv_timeout(dur_till_then(test_deadline)) {
Err(mpsc::RecvTimeoutError::Timeout) => {
println!("Timeout: Could not finish test of {}, shutting down", scen.display());
break;
},
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
println!("Error testing {}, OC seems to have died.", scen.display());
break;
},
Ok(msg) => {
match status_re.captures(&msg) {
None => {},
Some(caps) => {
println!("{}: {}", scen.display(), caps.get(1).unwrap().as_str())
},
};
match load_done_re.find(&msg) {
None => {},
Some(_) => {
break;
},
};
},
};
}
let finish_deadline = Instant::now() + shutdown_to;
if verbosity >= 1 {
println!("shutting down");
}
// Shut down
drop(process.stdin.take());
loop {
match ocout.recv_timeout(dur_till_then(finish_deadline)) {
Err(mpsc::RecvTimeoutError::Timeout) => {
println!("Error testing {}: Did not shut down within deadline. Killing.", scen.display());
process.kill().expect("Failure to kill OC.");
break;
},
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
break; // That should be a clean exit
},
Ok(_) => {},
};
}
let status_code = match process.wait_timeout(dur_till_then(finish_deadline)).unwrap() {
Some(status) => status.code(),
None => {
if verbosity >= 1 {
println!("Process not dead yet, killing more.");
}
process.kill().unwrap();
process.wait().unwrap().code()
}
};
if verbosity >= 1 {
println!("exited with: {}", status_code.map(|c| c.to_string()).as_ref().map(|x| &**x).unwrap_or("[no code]"))
}
}
fn cont_read(pipe : std::process::ChildStdout, print : bool) -> std::sync::mpsc::Receiver<String> {
// Rust is unfinished… :/ (Or I'm too stupid to find a way to do it without an extra thread.)
// Either way, I need this workaround.
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let mut reader = BufReader::new(pipe);
loop {
let mut buffer = String::new();
let num_bytes = reader.read_line(&mut buffer).unwrap();
if num_bytes < 1 {
return
}
if print {
print!(">> {}", buffer);
}
match tx.send(buffer) {
Err(_) => return,
Ok(_) => {},
};
}
});
return rx;
}
fn dur_till_then(t : std::time::Instant) -> std::time::Duration {
let now = Instant::now();
if t < now {
return Duration::from_secs(0);
}
return t - now;
}