use crate::OWL_DIR;
use crate::common::{OwlError, Result};
use crate::owl_utils::{cmd_utils, fs_utils, prog_utils};
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::Duration;
pub async fn quest(
quest_name: &str,
prog: &Path,
case_id: Option<usize>,
use_hints: bool,
) -> Result<()> {
let quest_path = fs_utils::ensure_path_from_home(&[OWL_DIR], Some(quest_name))?;
if !quest_path.exists() {
super::fetch_quest(quest_name).await?;
}
if !prog.exists() {
return Err(OwlError::FileError(
format!("'{}': no such file", prog.to_string_lossy()),
"".into(),
));
}
let (target, build_files) = match prog_utils::build_program(prog)? {
Some(bl) => (bl.target, bl.build_files),
None => (prog.to_path_buf(), None),
};
let test_cases: Vec<PathBuf> = fs_utils::find_by_ext(&quest_path, "in")?;
let total = test_cases.len();
let mut passed = 0;
let mut failed = 0;
let mut total_duration: Option<Duration> = None;
let (start, end, mut count) = match case_id {
Some(d) => (d, d + 1, d - 1),
None => (0, total, 0),
};
for test_case in test_cases.iter().skip(count).take(end - start) {
count += 1;
if let Some(d) = case_id
&& (count % total) != (d % total)
{
continue;
}
match quest_it(&target, test_case, count, total, use_hints) {
Ok((true, elapsed)) => {
passed += 1;
total_duration = match (total_duration, elapsed) {
(Some(d), Some(elap_time)) => Some(d + elap_time),
(Some(d), _) => Some(d),
_ => elapsed,
};
}
Ok((false, _)) | Err(_) => failed += 1,
}
}
println!(
"passed: {}, failed: {}, elapsed: {}ms",
passed,
failed,
total_duration.map(|d| d.as_millis()).unwrap_or(0)
);
prog_utils::cleanup_program(prog, &target, build_files)?;
if failed > 0 {
Err(OwlError::TestFailure("test failures".into()))
} else {
println!("\x1b[32mall tests passed\x1b[0m 🏆🏆🏆\n");
Ok(())
}
}
pub fn quest_it(
target: &Path,
test_case: &Path,
count: usize,
total: usize,
use_hints: bool,
) -> Result<(bool, Option<Duration>)> {
let in_stem = test_case
.file_stem()
.and_then(OsStr::to_str)
.ok_or(OwlError::UriError(
format!("'{}': has no file stem", test_case.to_string_lossy()),
"".into(),
))?;
let mut ans_path = test_case
.parent()
.ok_or(OwlError::FileError(
format!(
"Failed to determine parent dir of '{}'",
test_case.to_string_lossy()
),
"None".into(),
))?
.to_path_buf();
let ans_str = format!("{}.ans", in_stem);
ans_path.push(&ans_str);
if !ans_path.exists() {
ans_path.pop();
let out_str = format!("{}.out", in_stem);
ans_path.push(out_str);
}
if !ans_path.exists() {
return Err(OwlError::FileError(
format!(
"Failed to find answer for '{}' using stem '{}.ans' or '{}.out'",
test_case.to_string_lossy(),
in_stem,
in_stem
),
"".into(),
));
}
match super::test_it(target, test_case, &ans_path) {
Ok(elapsed) => {
println!(
"({}/{}) [{}ms] test_name: \x1b[36m{}\x1b[0m, status: \x1b[32mpassed test\x1b[0m 🎉\n",
count,
total,
elapsed.as_millis(),
in_stem
);
Ok((true, Some(elapsed)))
}
Err(e) => {
if use_hints && let Some(parent_dir) = test_case.parent() {
let feedback_file = format!("{}.md", in_stem);
let mut feedback_path = parent_dir.to_path_buf();
feedback_path.push(feedback_file);
cmd_utils::bat_file(&feedback_path).or_else(|_| {
cmd_utils::glow_file(&feedback_path).or_else(|_| {
fs::read_to_string(&feedback_path)
.map(|contents| eprintln!("{}", contents))
.map_err(|e| {
OwlError::FileError(
format!("could not read '{}'", feedback_path.to_string_lossy()),
e.to_string(),
)
})
})
})?
}
eprintln!(
"({}/{}) test_name: \x1b[36m{}\x1b[0m, status: \x1b[31m{}\x1b[0m 😭\n",
count, total, in_stem, e
);
Ok((false, None))
}
}
}
pub async fn quest_once(
quest_name: &str,
prog: &Path,
test_name: &str,
use_hints: bool,
) -> Result<()> {
let quest_path = fs_utils::ensure_path_from_home(&[OWL_DIR], Some(quest_name))?;
if !quest_path.exists() {
super::fetch_quest(quest_name).await?;
}
if !prog.exists() {
return Err(OwlError::FileError(
format!("'{}': no such file", prog.to_string_lossy()),
"".into(),
));
}
let (target, build_files) = match prog_utils::build_program(prog)? {
Some(bl) => (bl.target, bl.build_files),
None => (prog.to_path_buf(), None),
};
let in_path = fs_utils::find_by_stem_and_ext(&quest_path, test_name, "in")?;
let mut passed = 0;
let mut check_elapsed: Option<Duration> = None;
if let Ok((true, some_duration)) = quest_it(&target, &in_path, 0, 1, use_hints) {
passed = 1;
check_elapsed = some_duration;
}
println!(
"passed: {}, failed: {}, elapsed: {}ms",
passed,
1 - passed,
check_elapsed.map(|d| d.as_millis()).unwrap_or(0)
);
prog_utils::cleanup_program(prog, &target, build_files)?;
if passed == 0 {
Err(OwlError::TestFailure("test failures".into()))
} else {
println!("\x1b[32mall tests passed\x1b[0m 🏆🏆🏆\n");
Ok(())
}
}