use ccode_runner::lang_runner::language_name::LanguageName;
use ccode_runner::lang_runner::runner::Language;
use colored::Colorize;
use error_types::cli_error::CliErrorType;
use futures::future::join_all;
use std::io;
use std::path::Path;
use std::sync::Arc;
use ccode_runner::lang_runner::program_store::ProgramStore;
use ccode_runner::lang_runner::runner_error_types::RunnerErrorType;
use clex_gen::clex_language::clex_error_type::ClexErrorType;
use clex_gen::clex_language::{code_generator, lexer, parser};
use std::sync::atomic::{AtomicBool, Ordering};
pub(crate) mod error_types;
pub const DEFAULT_FAIL_EXIT_CODE: i32 = 1;
#[derive(thiserror::Error, Debug)]
pub enum GenericCpastError {
#[error("{0}")]
ClexError(#[from] ClexErrorType),
#[error("{0}")]
RunnerError(#[from] Box<RunnerErrorType>),
#[error("{0}")]
CliError(#[from] CliErrorType),
}
pub enum CodeOrPath {
Code(String, LanguageName),
Path(String),
}
pub async fn compile_and_test(
correct_binding: CodeOrPath,
test_binding: String,
language: String,
iterations: usize,
no_stop: bool,
do_force_compile: bool,
debug: bool,
) -> Result<(), GenericCpastError> {
let store = match correct_binding {
CodeOrPath::Code(correct_code, correct_lang) => {
let correct_lang_instance =
Language::new_from_text(&correct_code, correct_lang, do_force_compile)?;
let test_lang_instance = Language::new(Path::new(&test_binding), do_force_compile)?;
ProgramStore::new_from_language(correct_lang_instance, test_lang_instance)?
}
CodeOrPath::Path(correct_path) => ProgramStore::new(
Path::new(&correct_path),
Path::new(&test_binding),
do_force_compile,
)?,
};
let store = Arc::new(store);
let mut token = lexer::Tokens::new(language);
token.scan_tokens()?;
let mut parser = parser::Parser::new_from_tokens(token);
parser.parser()?;
let generator = code_generator::Generator::new(&parser);
let generator = Arc::new(generator);
let has_failed = Arc::new(AtomicBool::new(false));
if debug {
eprintln!("{}", "Debug mode enabled!".bold().yellow());
eprintln!(
"{}\n",
"[INFO] Using multi-threading to speed up the process, testcase order might vary!"
.bright_blue()
);
}
let tasks = (1..=iterations)
.map(|iter| {
let has_failed_clone = Arc::clone(&has_failed);
let store_clone = Arc::clone(&store);
let generator_clone = Arc::clone(&generator);
tokio::spawn(async move {
process_test_case(
no_stop,
debug,
iter,
has_failed_clone,
store_clone,
generator_clone,
)
.await;
})
})
.collect::<Vec<_>>();
join_all(tasks).await;
if no_stop {
println!(
"\n{}",
"Test case generation & matching done!".bold().bright_blue()
);
}
if !has_failed.load(Ordering::Relaxed) {
println!("{}", "🐣 Vohoo! No testcases have failed!".bold().green());
}
Ok(())
}
async fn process_test_case(
no_stop: bool,
debug: bool,
iter: usize,
has_failed_clone: Arc<AtomicBool>,
store_clone: Arc<ProgramStore>,
generator_clone: Arc<code_generator::Generator>,
) {
if !no_stop && has_failed_clone.load(Ordering::Relaxed) {
return;
}
match generator_clone.generate_testcases() {
Err(err) => {
eprintln!("{err}");
has_failed_clone.store(true, Ordering::Relaxed);
}
Ok(output_text) => match store_clone.run_codes_and_compare_output(&output_text) {
Ok((true, _, _)) => {
if !no_stop && debug {
eprintln!("{}", format!("Testcase {iter} ran successfully!").green());
}
}
Ok((false, expected, actual)) => {
println!(
"{}\n{}\n{}\n==============================\n{}\n{}\n==============================\n{}\n{}",
format!("Testcase {iter} failed!").red(),
"INPUT".underline(),
&output_text.cyan(),
"EXPECTED OUTPUT".underline(),
expected.green(),
"ACTUAL OUTPUT".underline(),
actual.red()
);
has_failed_clone.store(true, Ordering::Relaxed);
}
Err(err) => {
eprintln!("{}", format!("Error matching the file! {err}").red());
if let RunnerErrorType::ProgramRunError(run_err) = *err
&& let Some(io_err) = run_err.downcast_ref::<io::Error>()
&& io_err.kind() == io::ErrorKind::BrokenPipe
{
eprintln!("Broken pipe detected!");
eprintln!(
"This usually happens when your clex is incorrect and it doesn't generate what your codes are expecting!"
);
eprintln!("Please check your clex and try again!");
}
has_failed_clone.store(true, Ordering::Relaxed);
}
},
}
}