1use ccode_runner::lang_runner::language_name::LanguageName;
34use ccode_runner::lang_runner::runner::Language;
35use colored::Colorize;
36use error_types::cli_error::CliErrorType;
37use futures::future::join_all;
38use std::io;
39use std::path::Path;
40use std::sync::Arc;
41
42use ccode_runner::lang_runner::program_store::ProgramStore;
43use ccode_runner::lang_runner::runner_error_types::RunnerErrorType;
44use clex_gen::clex_language::clex_error_type::ClexErrorType;
45use clex_gen::clex_language::{code_generator, lexer, parser};
46use std::sync::atomic::{AtomicBool, Ordering};
47
48pub(crate) mod error_types;
49
50pub const DEFAULT_FAIL_EXIT_CODE: i32 = 1;
51
52#[derive(thiserror::Error, Debug)]
53pub enum GenericCpastError {
54 #[error("{0}")]
55 ClexError(#[from] ClexErrorType),
56
57 #[error("{0}")]
58 RunnerError(#[from] Box<RunnerErrorType>),
59
60 #[error("{0}")]
61 CliError(#[from] CliErrorType),
62}
63
64pub enum CodeOrPath {
65 Code(String, LanguageName),
67 Path(String),
69}
70
71pub async fn compile_and_test(
91 correct_binding: CodeOrPath,
92 test_binding: String,
93 language: String,
94 iterations: usize,
95 no_stop: bool,
96 do_force_compile: bool,
97 debug: bool,
98) -> Result<(), GenericCpastError> {
99 let store = match correct_binding {
100 CodeOrPath::Code(correct_code, correct_lang) => {
101 eprintln!(
103 "{}",
104 "Using --problem_url is unstable at this moment. It may or may not work!"
105 .bold()
106 .red()
107 );
108 let correct_lang_instance =
109 Language::new_from_text(&correct_code, correct_lang, do_force_compile)?;
110 let test_lang_instance = Language::new(Path::new(&test_binding), do_force_compile)?;
111 ProgramStore::new_from_language(correct_lang_instance, test_lang_instance)?
112 }
113 CodeOrPath::Path(correct_path) => ProgramStore::new(
114 Path::new(&correct_path),
115 Path::new(&test_binding),
116 do_force_compile,
117 )?,
118 };
119 let store = Arc::new(store);
120
121 let mut token = lexer::Tokens::new(language);
122 token.scan_tokens()?;
123 let mut parser = parser::Parser::new_from_tokens(token);
124 parser.parser()?;
125 let generator = code_generator::Generator::new(&parser);
126 let generator = Arc::new(generator);
127
128 let has_failed = Arc::new(AtomicBool::new(false));
129 if debug {
132 eprintln!("{}", "Debug mode enabled!".bold().yellow());
133 eprintln!(
134 "{}\n",
135 "[INFO] Using multi-threading to speed up the process, testcase order might vary!"
136 .bright_blue()
137 );
138 }
139
140 let tasks = (1..=iterations)
141 .map(|iter| {
142 let has_failed_clone = Arc::clone(&has_failed);
143 let store_clone = Arc::clone(&store);
144 let generator_clone = Arc::clone(&generator);
145 tokio::spawn(async move {
148 process_test_case(
150 no_stop,
151 debug,
152 iter,
153 has_failed_clone,
154 store_clone,
155 generator_clone,
156 )
157 .await;
158 })
160 })
161 .collect::<Vec<_>>();
162
163 join_all(tasks).await;
164
165 if no_stop {
166 println!(
167 "\n{}",
168 "Test case generation & matching done!".bold().bright_blue()
169 );
170 }
171
172 if !has_failed.load(Ordering::Relaxed) {
173 println!("{}", "🐣 Vohoo! No testcases have failed!".bold().green());
174 }
175
176 Ok(())
177}
178
179async fn process_test_case(
180 no_stop: bool,
181 debug: bool,
182 iter: usize,
183 has_failed_clone: Arc<AtomicBool>,
184 store_clone: Arc<ProgramStore>,
185 generator_clone: Arc<code_generator::Generator>,
186) {
187 if !no_stop && has_failed_clone.load(Ordering::Relaxed) {
188 return;
189 }
190
191 match generator_clone.generate_testcases() {
192 Err(err) => {
193 eprintln!("{}", err);
194 has_failed_clone.store(true, Ordering::Relaxed);
195 }
196 Ok(output_text) => match store_clone.run_codes_and_compare_output(&output_text) {
197 Ok((true, _, _)) => {
198 if !no_stop && debug {
199 eprintln!("{}", format!("Testcase {} ran successfully!", iter).green());
200 }
201 }
202 Ok((false, expected, actual)) => {
203 println!(
204 "{}\n{}\n{}\n==============================\n{}\n{}\n==============================\n{}\n{}",
205 format!("Testcase {} failed!", iter).red(),
206 "INPUT".underline(),
207 &output_text.cyan(),
208 "EXPECTED OUTPUT".underline(),
209 expected.green(),
210 "ACTUAL OUTPUT".underline(),
211 actual.red()
212 );
213 has_failed_clone.store(true, Ordering::Relaxed);
214 }
215 Err(err) => {
216 eprintln!("{}", format!("Error matching the file! {}", err).red());
217 if let RunnerErrorType::ProgramRunError(run_err) = *err {
218 if let Some(io_err) = run_err.downcast_ref::<io::Error>() {
219 if io_err.kind() == io::ErrorKind::BrokenPipe {
220 eprintln!("Broken pipe detected!");
221 eprintln!(
222 "This usually happens when your clex is incorrect and it doesn't generate what your codes are expecting!"
223 );
224 eprintln!("Please check your clex and try again!");
225 }
226 }
227 }
228
229 has_failed_clone.store(true, Ordering::Relaxed);
230 }
231 },
232 }
233}