use std::path::{PathBuf, Path}; use std::process::Stdio; use std::{process, fs}; use clap::Parser; use color_eyre::Result; use eyre::eyre; pub mod color { #![allow(dead_code)] pub const NONE: &str = "\x1b[0m"; pub const RESET: &str = "\x1b[0m"; pub const BRIGHT: &str = "\x1b[1m"; pub const DIM: &str = "\x1b[2m"; pub const UNDERSCORE: &str = "\x1b[4m"; pub const BLINK: &str = "\x1b[5m"; pub const REVERSE: &str = "\x1b[7m"; pub const HIDDEN: &str = "\x1b[8m"; pub const FG_BLACK: &str = "\x1b[30m"; pub const FG_RED: &str = "\x1b[31m"; pub const FG_GREEN: &str = "\x1b[32m"; pub const FG_YELLOW: &str = "\x1b[33m"; pub const FG_BLUE: &str = "\x1b[34m"; pub const FG_MAGENTA: &str = "\x1b[35m"; pub const FG_CYAN: &str = "\x1b[36m"; pub const FG_WHITE: &str = "\x1b[37m"; pub const BG_BLACK: &str = "\x1b[40m"; pub const BG_RED: &str = "\x1b[41m"; pub const BG_GREEN: &str = "\x1b[42m"; pub const BG_YELLOW: &str = "\x1b[43m"; pub const BG_BLUE: &str = "\x1b[44m"; pub const BG_MAGENTA: &str = "\x1b[45m"; pub const BG_CYAN: &str = "\x1b[46m"; pub const BG_WHITE: &str = "\x1b[47m"; } #[allow(dead_code)] #[derive(Debug)] struct TestOutput { stdout: String, stderr: String, stdin: String, status: i32 } fn run_test + std::convert::AsRef>(f_in: PathBuf, f_out: &PathBuf, compiler: P, compile_mode: bool, stdin: String) -> Result { let mut command = process::Command::new(compiler); command.stdout(Stdio::piped()); command.stderr(Stdio::piped()); if compile_mode { command.arg("-cqr"); } else { command.arg("-sq"); } command.arg("-i"); command.arg(f_in); command.arg("-o"); command.arg(f_out); let child = command.spawn()?; let out = child.wait_with_output()?; let stdout = out.stdout.iter().map(|c| { char::from_u32(u32::from(*c)).expect("Failed to parse stdout char").to_string() }).collect::(); let stderr = out.stderr.iter().map(|c| { char::from_u32(u32::from(*c)).expect("Failed to parse stderr char").to_string() }).collect::(); Ok(TestOutput { stdout, stderr, stdin, status: out.status.code().unwrap() }) } fn run_tests(args: Args) -> Result<()>{ let files = fs::read_dir(args.input)?; for file in files { let file = file?; let f_name = file.file_name().to_string_lossy().to_string(); let f_out = PathBuf::from(&args.output).join(f_name); let intp = run_test(file.path(), &f_out, &args.compiler_path, false, String::new())?; let comp = run_test(file.path(), &f_out, &args.compiler_path, true, String::new())?; compare_results(&intp, &comp, &file.path())?; } Ok(()) } fn compare_results(intp: &TestOutput, comp: &TestOutput, f_in: &Path) -> Result<()> { if intp.stdout != comp.stdout { println!("{b}[ {r}ERR{rs}{b} ]{rs} {f} compiled and interpreted stdout versions differ", r=color::FG_RED, rs=color::RESET, b=color::BRIGHT, f=f_in.display()); println!("compiled:\n{}", comp.stdout); println!("interpreted:\n{}", intp.stdout); return Err(eyre!("Testing failed")); } if intp.stderr != comp.stderr { println!("{b}[ {r}ERR{rs}{b} ]{rs} {f} compiled and interpreted stderr versions differ", r=color::FG_RED, rs=color::RESET, b=color::BRIGHT, f=f_in.display()); println!("compiled:\n{}", comp.stderr); println!("interpreted:\n{}", intp.stderr); return Err(eyre!("Testing failed")); } if intp.status != comp.status { println!("{b}[ {r}ERR{rs}{b} ]{rs} {f} compiled and interpreted status codes differ", r=color::FG_RED, rs=color::RESET, b=color::BRIGHT, f=f_in.display()); println!("compiled:\n{}", comp.status); println!("interpreted:\n{}", intp.status); return Err(eyre!("Testing failed")); } println!("{b}[ {g}OK{rs}{b} ]{rs} {f} ", g=color::FG_GREEN, rs=color::RESET, b=color::BRIGHT, f=f_in.display()); Ok(()) } #[derive(Parser, Debug, Clone)] #[command(author, version, about, long_about = None)] struct Args { /// Mode, allowed modes: test, record #[arg(long, short)] mode: String, /// Use compile mode #[arg(long, short)] compile: bool, /// Use interpret mode #[arg(long, short='s')] interpret: bool, /// Output folder #[arg(long, short, default_value_t=String::from("./target/mcl_test_dev"))] output: String, /// Input folder #[arg(long, short, default_value_t=String::from("./tests"))] input: String, /// Compiler path #[arg(long, short, default_value_t=String::from("./target/release/mclangc"))] compiler_path: String } fn main() -> Result<()> { let args = Args::parse(); fs::create_dir_all(&args.output)?; match args.mode.as_str() { "test" => run_tests(args), "record" => todo!("Implement test result recording"), s => { eprintln!("Unknown mode '{s}'"); return Err(eyre!("Bad subcommand")); } }?; Ok(()) }