mclangc/src/bin/test/main.rs
2024-12-21 23:08:45 +02:00

221 lines
6.8 KiB
Rust

use std::{collections::HashMap, ffi::OsStr, io::Write, os::unix::ffi::OsStrExt, path::{Path, PathBuf}, process::ExitCode};
use camino::Utf8PathBuf;
use clap::Parser;
use mclangc;
#[macro_use]
mod logger;
/// Testing program for mclangc, taken inspiration from porth, which was made by tsoding :3
#[derive(Debug, clap::Parser)]
#[command(version, about, long_about = None)]
struct CliArgs {
/// Path to the test folder
#[arg(long, short, default_value="./tests")]
path: Utf8PathBuf,
#[clap(subcommand)]
cmd: CliCmd
}
#[derive(Debug, clap::Subcommand)]
pub enum CliCmd {
/// Run the tests
Run,
/// Run the tests and set the output as the expected output
Compile
}
struct CollectedFiles {
tokeniser: HashMap<String, (String, ExpTyp)>,
parser: HashMap<String, (String, ExpTyp)>,
}
enum ExpTyp {
Text((PathBuf, String)),
Path(PathBuf),
}
impl ExpTyp {
pub fn path(&self) -> &Path {
match self {
Self::Text((p, _)) => p,
Self::Path(p) => p,
}
}
}
fn collect_files_for_single_type(path: &Path) -> anyhow::Result<HashMap<String, (String, ExpTyp)>> {
let mut files = HashMap::new();
for file in path.read_dir()? {
let file = file?;
if file.file_type()?.is_file() {
if file.path().extension() != Some(OsStr::from_bytes(b"mcl")) {
continue;
}
let src = std::fs::read_to_string(file.path())?;
let exp_p = file.path().with_extension("exp");
let name = file.path().with_extension("").file_name().unwrap().to_string_lossy().to_string();
if exp_p.exists() {
let exp = std::fs::read_to_string(&exp_p)?;
files.insert(name, (src, ExpTyp::Text((exp_p, exp))));
} else {
files.insert(name, (src, ExpTyp::Path(exp_p)));
}
}
}
Ok(files)
}
fn collect_all_files(path: &Path) -> anyhow::Result<CollectedFiles> {
let path = path.to_path_buf();
let mut tkn = path.clone();
tkn.push("tokeniser");
let mut parser = path.clone();
parser.push("parser");
Ok(CollectedFiles {
tokeniser: collect_files_for_single_type(&tkn)?,
parser: collect_files_for_single_type(&parser)?,
})
}
fn test_tokeniser(cf: &CollectedFiles, compile: bool) -> anyhow::Result<usize> {
let mut err_count = 0;
for (name, (src, expected)) in &cf.tokeniser {
let tokens = match mclangc::tokeniser::tokenise(src, &format!("tokeniser/{name}.mcl")) {
Ok(v) => v,
Err(e) => {
crate::error!("Test tokeniser/{name} had an error: {e}");
err_count += 1;
continue;
}
};
if compile {
let path = expected.path();
if path.exists() {
crate::info!("Test tokeniser/{name} already has a *.exp file, overwriting");
} else {
crate::info!("Test tokeniser/{name} doesnt a *.exp file, creating");
}
let mut fp = std::fs::File::options()
.write(true)
.truncate(true)
.create(true)
.open(path)?;
write!(fp, "{tokens:#?}")?;
} else {
let ExpTyp::Text((_, exp)) = expected else {
crate::warn!("Test tokeniser/{name} doesnt have a *.exp file, please make it by running 'test compile'");
continue;
};
if format!("{tokens:#?}") == *exp {
crate::info!("Test tokeniser/{name}: OK");
} else {
crate::error!("Test tokeniser/{name}: FAIL");
crate::debug!("Expected: {exp}");
crate::debug!("Got: {tokens:#?}");
err_count += 1;
}
}
}
Ok(err_count)
}
fn test_parser(cf: &CollectedFiles, compile: bool) -> anyhow::Result<usize> {
let mut err_count = 0;
for (name, (src, expected)) in &cf.parser {
let tokens = match mclangc::tokeniser::tokenise(src, &format!("parser/{name}.mcl")) {
Ok(v) => v,
Err(e) => {
crate::error!("Test parser/{name} had an error: {e}");
err_count += 1;
continue;
}
};
let ast = match mclangc::parser::parse_program(tokens) {
Ok(v) => v,
Err(e) => {
crate::error!("Test parser/{name} had an error: {e}");
err_count += 1;
continue;
}
};
if compile {
let path = expected.path();
if path.exists() {
crate::info!("Test parser/{name} already has a *.exp file, overwriting");
} else {
crate::info!("Test parser/{name} doesnt a *.exp file, creating");
}
let mut fp = std::fs::File::options()
.write(true)
.truncate(true)
.create(true)
.open(path)?;
write!(fp, "{ast:#?}")?;
} else {
let ExpTyp::Text((_, exp)) = expected else {
crate::warn!("Test parser/{name} doesnt have a *.exp file, please make it by running 'test compile'");
continue;
};
if format!("{ast:#?}") == *exp {
crate::info!("Test parser/{name}: OK");
} else {
crate::error!("Test parser/{name}: FAIL");
crate::debug!("Expected: {exp}");
crate::debug!("Got: {ast:#?}");
err_count += 1;
}
}
}
Ok(err_count)
}
fn test(cf: &CollectedFiles, compile: bool) -> anyhow::Result<usize> {
let mut err_count = test_tokeniser(&cf, compile)?;
err_count += test_parser(&cf, compile)?;
Ok(err_count)
}
fn main() -> ExitCode {
let cli = CliArgs::parse();
let cf = match collect_all_files(cli.path.as_std_path()) {
Ok(v) => v,
Err(e) => {
crate::error!("Failed to read directory '{}', do you have permission to read it?: {e}", cli.path);
return ExitCode::FAILURE;
}
};
let ec = match cli.cmd {
CliCmd::Run => {
match test(&cf, false) {
Ok(v) => v,
Err(e) => {
crate::error!("Had an error: {e}");
return ExitCode::FAILURE;
}
}
}
CliCmd::Compile => {
match test(&cf, true) {
Ok(v) => v,
Err(e) => {
crate::error!("Had an error: {e}");
return ExitCode::FAILURE;
}
}
}
};
if ec > 0 {
crate::error!("Testing FAILED, had {ec} errors");
return ExitCode::FAILURE;
} else {
crate::info!("Testing SUCCEEDED, had 0 errors");
}
ExitCode::SUCCESS
}