use crate::{
FileStatus, LeanFile, Result, TraceConfig, TraceRecord, TraceWriter, capture_provenance,
discover_lean_files, run_lean_file,
};
use camino::Utf8PathBuf;
use tracing::{info, warn};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct TraceSummary {
pub files_run: usize,
pub records_written: usize,
pub non_passing: usize,
}
pub async fn run_trace(
config: &TraceConfig,
roots: &[Utf8PathBuf],
writer: &mut TraceWriter,
) -> Result<TraceSummary> {
let provenance = capture_provenance(&config.lake_root).await;
info!(
lean_version = provenance.lean_version.as_deref().unwrap_or("unknown"),
lake_version = provenance.lake_version.as_deref().unwrap_or("unknown"),
git_commit = provenance.git_commit.as_deref().unwrap_or("none"),
"captured run provenance"
);
let files = collect_files(config, roots)?;
info!(count = files.len(), "discovered Lean files");
let mut summary = TraceSummary::default();
for file in files {
info!(%file, "checking file");
let trace = run_lean_file(config, &provenance, file).await;
let passed = trace.status == FileStatus::Passed;
if !passed {
summary.non_passing += 1;
warn!(file = %trace.file, status = ?trace.status, "file did not pass");
}
summary.files_run += 1;
if config.include_passes || !passed || !trace.diagnostics.is_empty() {
writer.write_record(&TraceRecord::FileTrace(trace))?;
summary.records_written += 1;
}
}
writer.flush()?;
Ok(summary)
}
fn collect_files(config: &TraceConfig, roots: &[Utf8PathBuf]) -> Result<Vec<LeanFile>> {
let mut files = Vec::new();
for root in roots {
files.extend(discover_lean_files(root, config.recursive)?);
}
files.sort();
files.dedup();
files.retain(|file| !is_excluded(file, &config.exclude));
Ok(files)
}
fn is_excluded(file: &LeanFile, exclude: &[String]) -> bool {
let path = file.as_path().as_str();
exclude
.iter()
.any(|pattern| path.contains(pattern.as_str()))
}
#[cfg(test)]
mod tests {
use super::*;
fn lean(path: &str) -> LeanFile {
LeanFile(Utf8PathBuf::from(path))
}
#[test]
fn exclude_matches_substring() {
let patterns = vec![".lake/".to_owned(), "Generated".to_owned()];
assert!(is_excluded(
&lean("project/.lake/build/Foo.lean"),
&patterns
));
assert!(is_excluded(&lean("src/GeneratedThing.lean"), &patterns));
assert!(!is_excluded(&lean("src/Real.lean"), &patterns));
}
#[test]
fn empty_exclude_keeps_everything() {
assert!(!is_excluded(&lean("anything.lean"), &[]));
}
}