use std::fs;
use std::io::Write;
use tempfile::tempdir;
use xbp_analysis::adapters::output::render_report;
use xbp_analysis::application::engine::AnalysisEngine;
use xbp_analysis::domain::request::{AnalysisRequest, OutputFormat};
fn write_rs(dir: &std::path::Path, name: &str, body: &str) {
let p = dir.join(name);
if let Some(parent) = p.parent() {
fs::create_dir_all(parent).unwrap();
}
let mut f = fs::File::create(p).unwrap();
write!(f, "{body}").unwrap();
}
#[test]
fn positive_unwrap_and_panic() {
let dir = tempdir().unwrap();
write_rs(
dir.path(),
"bad.rs",
r#"
fn boom() {
let x = Ok::<i32, ()>(1).unwrap();
panic!("nope");
let _ = x;
}
"#,
);
let engine = AnalysisEngine::new();
let mut req = AnalysisRequest::new(dir.path());
req.language_filter = Some(xbp_analysis::LanguageId::rust());
let report = engine.analyze(&req).unwrap();
assert!(
report.findings.iter().any(|f| f.rule_id == "unsafe-termination"),
"expected unsafe-termination, got {:?}",
report.findings.iter().map(|f| &f.rule_id).collect::<Vec<_>>()
);
}
#[test]
fn negative_proper_propagation() {
let dir = tempdir().unwrap();
write_rs(
dir.path(),
"good.rs",
r#"
fn ok() -> Result<(), std::io::Error> {
let data = std::fs::read("x")?;
let _ = data.len();
Ok(())
}
"#,
);
let engine = AnalysisEngine::new();
let mut req = AnalysisRequest::new(dir.path());
req.language_filter = Some(xbp_analysis::LanguageId::rust());
req.config.rules.disabled = vec![
"error-context-loss".into(),
"missing-cleanup".into(),
];
let report = engine.analyze(&req).unwrap();
assert!(
!report
.findings
.iter()
.any(|f| f.rule_id == "ignored-failure" || f.rule_id == "unsafe-termination"),
"unexpected {:?}",
report.findings
);
}
#[test]
fn json_and_sarif_render() {
let dir = tempdir().unwrap();
write_rs(dir.path(), "u.rs", "fn f() { let _ = Ok::<(),()>(()).unwrap(); }\n");
let engine = AnalysisEngine::new();
let req = AnalysisRequest::new(dir.path());
let report = engine.analyze(&req).unwrap();
let json = render_report(&report, OutputFormat::Json).unwrap();
assert!(json.contains("findings"));
let sarif = render_report(&report, OutputFormat::Sarif).unwrap();
assert!(sarif.contains("2.1.0"));
let term = render_report(&report, OutputFormat::Terminal).unwrap();
assert!(term.contains("xbp audit"));
}
#[test]
fn determinism_two_runs() {
let dir = tempdir().unwrap();
write_rs(
dir.path(),
"a.rs",
"fn a() { Ok::<(),()>(()).unwrap(); }\nfn b() { todo!(); }\n",
);
let engine = AnalysisEngine::new();
let req = AnalysisRequest::new(dir.path());
let r1 = engine.analyze(&req).unwrap();
let r2 = engine.analyze(&req).unwrap();
let ids1: Vec<_> = r1.findings.iter().map(|f| f.id.clone()).collect();
let ids2: Vec<_> = r2.findings.iter().map(|f| f.id.clone()).collect();
assert_eq!(ids1, ids2);
}
#[test]
fn performance_many_files() {
let dir = tempdir().unwrap();
for i in 0..50 {
write_rs(
dir.path(),
&format!("f{i}.rs"),
&format!("fn f{i}() -> Result<(),()> {{ Ok(()) }}\n"),
);
}
let engine = AnalysisEngine::new();
let req = AnalysisRequest::new(dir.path());
let started = std::time::Instant::now();
let report = engine.analyze(&req).unwrap();
let ms = started.elapsed().as_millis();
assert_eq!(report.files_analyzed, 50);
assert!(ms < 30_000, "analysis took {ms}ms");
}