use serde::{Deserialize, Serialize}; use std::{ fs::{self, read_to_string, File}, path::PathBuf, process::{Command, Stdio}, time::Instant, }; pub fn main() { let mut args: Vec = std::env::args().collect(); // name of the process to run let proc = args.remove(1); if args.len() > 1 { let run = traverse(&args) .and_then(|paths| run_test(&proc, &paths)) .expect("Failed to traverse directory"); println!("{}", serde_json::to_string_pretty(&run).unwrap()); } else { println!( r#"Usage: tester [options] + Options: -p, --process The process to run. If the given process is not a an absolute path, it will be resolved against the PATH environment variable. -j, --json Output the results in JSON format (default: false) -h, --help Display this help message -v, --version Display the version of the tester "# ); } } fn traverse(args: &[String]) -> Result, String> { let mut files: Vec = Vec::new(); for arg in args.iter().skip(1) { let entries = fs::read_dir(arg).map_err(|e| e.to_string())?; for entry in entries { let dir = entry.map_err(|e| e.to_string())?; let f = dir.metadata().map_err(|e| e.to_string())?; if f.is_dir() { files.push(dir.path()); } } } Ok(files) } #[derive(Debug, Serialize, Deserialize)] pub enum TestResult { TestSucceeded, TestFailed(String, String), } #[derive(Debug, Serialize, Deserialize)] pub struct TestRun { file: String, test_result: TestResult, duration: u128, } fn run_test(proc: &str, files: &Vec) -> Result, String> { let mut result = Vec::new(); for file in files { let mut inp = file.clone(); let mut outp = file.clone(); inp.push("input"); outp.push("output"); let (test_result, duration) = run_test_case(proc, &inp, &outp)?; result.push(TestRun { file: inp.as_path().to_str().unwrap().to_string(), test_result, duration, }); } Ok(result) } fn run_test_case( proc: &str, inp: &std::path::PathBuf, outp: &std::path::PathBuf, ) -> Result<(TestResult, u128), String> { let input = File::open(inp).map_err(|e| e.to_string())?; let expected = read_to_string(outp).map_err(|e| e.to_string())?; let start = Instant::now(); let actual = Command::new(proc) .stdin(Stdio::from(input)) .output() .map_err(|e| e.to_string())?; let duration = (Instant::now() - start).as_millis(); if expected.as_bytes() == actual.stdout { Ok((TestResult::TestSucceeded, duration)) } else { let actual_string = String::from_utf8(actual.stdout).map_err(|e| e.to_string())?; Ok((TestResult::TestFailed(expected, actual_string), duration)) } }