use crate::{CheckInfo, Htl, parent_dir, write_if_changed};
use anyhow::{Context, Result};
use mlua::{Function, Table, Value};
use std::path::{Path, PathBuf};
const TEST_LUA: &str = include_str!("../lua/test.lua");
const TEST_DTL: &str = include_str!("../lua/test.d.tl");
pub const DEFAULT_LIB: &str = "htl.test";
pub fn lib_dir() -> Result<PathBuf> {
let dir = std::env::temp_dir().join(format!("htl-lib-{}", env!("CARGO_PKG_VERSION")));
write_if_changed(&dir.join("htl").join("test.d.tl"), TEST_DTL)
.with_context(|| format!("writing bundled declarations under {}", dir.display()))?;
Ok(dir)
}
impl Htl {
pub fn install_test_lib(&self) -> Result<()> {
self.preload(DEFAULT_LIB, TEST_LUA)?;
self.add_path(&lib_dir()?)?;
Ok(())
}
}
#[derive(Debug, Default)]
pub struct FileReport {
pub path: PathBuf,
pub check: CheckInfo,
pub error: Option<String>,
pub passed: usize,
pub failed: usize,
pub failures: Vec<String>,
pub file_level: bool,
}
impl FileReport {
pub fn ok(&self) -> bool {
self.check.ok() && self.error.is_none() && self.failed == 0
}
}
pub fn discover_tests(paths: &[PathBuf]) -> Result<Vec<PathBuf>> {
let mut out = Vec::new();
for p in paths {
if p.is_file() {
out.push(p.clone());
continue;
}
let extra = crate::project_skip_dirs(p);
let root = p.clone();
let walker = walkdir::WalkDir::new(p)
.sort_by_file_name()
.into_iter()
.filter_entry(move |e| e.path() == root || !crate::is_skipped_dir(e.path(), &extra));
for e in walker {
let e = e?;
let path = e.path();
if !crate::is_tl_source(path) {
continue;
}
let name = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
let in_tests_dir = path
.strip_prefix(p)
.ok()
.map(|rel| rel.components().any(|c| c.as_os_str() == "tests"))
.unwrap_or(false);
if name.ends_with("_test.tl") || in_tests_dir {
out.push(path.to_path_buf());
}
}
}
out.sort();
out.dedup();
Ok(out)
}
pub fn run_test_file(path: &Path, filter: Option<&str>, lib: &str, lint_spec: Option<&str>) -> Result<FileReport> {
let mut rep = FileReport { path: path.to_path_buf(), ..Default::default() };
let h = Htl::new()?;
if let Some(spec) = lint_spec {
h.configure_lints(spec)?;
}
h.install_test_lib()?;
let dir = parent_dir(path);
h.add_path(&dir)?;
#[cfg(feature = "pkg")]
if let Some(p) = crate::pkg::Project::find(path) {
h.apply_project(&p)?;
}
if dir.file_name().is_some_and(|n| n == "tests")
&& let Some(root) = dir.parent()
{
h.add_path(root)?;
h.add_path(&root.join("src"))?;
}
h.install_searcher()?;
h.set_arg(&path.to_string_lossy(), &[])?;
let (code, check) = h.gen_lua(path)?;
rep.check = check;
let Some(code) = code else { return Ok(rep) };
if let Err(e) = h.exec(&code, &format!("@{}", path.display()), &[]) {
rep.error = Some(crate::user_message(&e));
return Ok(rep);
}
let package: Table = h.lua().globals().get("package")?;
let loaded: Table = package.get("loaded")?;
match loaded.get::<Value>(lib)? {
Value::Table(t) => {
let run: Function = t.get("run")?;
let report: Table = run.call(filter)?;
rep.passed = report.get::<Option<usize>>("passed")?.unwrap_or(0);
rep.failed = report.get::<Option<usize>>("failed")?.unwrap_or(0);
if let Ok(f) = report.get::<Table>("failures") {
rep.failures = f.sequence_values::<String>().collect::<mlua::Result<_>>()?;
}
}
_ => {
rep.file_level = true;
rep.passed = 1;
}
}
Ok(rep)
}