use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
use camel_cli::commands::lint::{is_stagec_exempt_path, production_engine};
use camel_lint::DiagnosticCode;
type CodeSev = (String, String);
type EmittedMap = BTreeMap<String, BTreeSet<CodeSev>>;
fn is_build_artifact(p: &Path) -> bool {
p.components().any(|c| c.as_os_str() == "target")
}
fn workspace_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.canonicalize()
.expect("workspace root canonicalizes")
}
fn collect(pattern: &str, out: &mut BTreeSet<PathBuf>) {
for entry in glob::glob(pattern).expect("glob pattern compiles") {
match entry {
Ok(p) => {
out.insert(p);
}
Err(e) => panic!("glob iteration error on `{pattern}`: {e}"),
}
}
}
fn discover_corpus() -> Vec<(String, PathBuf)> {
let root = workspace_root();
let mut found = BTreeSet::new();
for ext in ["yaml", "yml", "json"] {
let pat = root
.join("examples")
.join("**")
.join(format!("*.{ext}"))
.to_string_lossy()
.into_owned();
collect(&pat, &mut found);
let pat = root
.join("crates")
.join("**")
.join("tests")
.join("fixtures")
.join("**")
.join(format!("*.{ext}"))
.to_string_lossy()
.into_owned();
collect(&pat, &mut found);
}
let excluded = root.join("examples").join("validator").join("schemas");
found
.into_iter()
.filter(|p| !p.starts_with(&excluded))
.filter(|p| !camel_dsl::discovery::is_test_document(p))
.filter(|p| !is_build_artifact(p))
.map(|p| {
let rel = p
.strip_prefix(&root)
.unwrap_or_else(|_| panic!("corpus path {p:?} not under workspace root {root:?}"))
.to_string_lossy()
.into_owned();
(rel, p)
})
.collect()
}
async fn run_corpus() -> EmittedMap {
let engine = production_engine()
.await
.expect("production engine builds for corpus gate");
let corpus = discover_corpus();
assert!(
!corpus.is_empty(),
"corpus glob found zero files — discovery is broken"
);
let mut emitted: EmittedMap = BTreeMap::new();
for (rel, full) in &corpus {
let source = std::fs::read_to_string(full)
.unwrap_or_else(|e| panic!("read corpus file {full:?}: {e}"));
let exempt = is_stagec_exempt_path(full);
for diag in engine.lint(&source) {
if exempt && diag.code == DiagnosticCode::RMock {
continue;
}
emitted
.entry(rel.clone())
.or_default()
.insert((diag.code.to_string(), diag.severity.to_string()));
}
}
emitted
}
fn load_baseline() -> EmittedMap {
let path =
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/lint-corpus-baseline.ron");
let text =
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read baseline {path:?}: {e}"));
let list: Vec<(String, Vec<CodeSev>)> =
ron::from_str(&text).unwrap_or_else(|e| panic!("parse baseline RON: {e}"));
let mut map: EmittedMap = BTreeMap::new();
for (file, codes) in list {
for cs in codes {
map.entry(file.clone()).or_default().insert(cs);
}
}
map
}
fn compare(emitted: &EmittedMap, baseline: &EmittedMap) -> Vec<String> {
let mut failures = Vec::new();
for (file, codes) in emitted {
let base = baseline.get(file);
for cs in codes {
if !base.is_some_and(|b| b.contains(cs)) {
failures.push(format!(
"FALSE-POSITIVE: {file} emits {}({}) not in baseline",
cs.0, cs.1
));
}
}
}
for (file, codes) in baseline {
let got = emitted.get(file);
for cs in codes {
if !got.is_some_and(|g| g.contains(cs)) {
failures.push(format!(
"MISSING-REGRESSION: {file} baseline {}({}) not emitted",
cs.0, cs.1
));
}
}
}
failures
}
#[tokio::test]
async fn corpus_zero_false_positives() {
let emitted = run_corpus().await;
let baseline = load_baseline();
let failures = compare(&emitted, &baseline);
if !failures.is_empty() {
panic!(
"corpus gate failed ({} mismatch(es)):\n - {}\n\n\
If these are real defects, add them to \
tests/fixtures/lint-corpus-baseline.ron with a justification.\n\
If they are suspected false positives, report them — do NOT \
baseline.",
failures.len(),
failures.join("\n - ")
);
}
}
#[tokio::test]
async fn corpus_gate_detects_false_positive() {
let mut emitted = run_corpus().await;
let baseline = load_baseline();
let probe_file = emitted
.keys()
.next()
.cloned()
.expect("corpus non-empty for FP probe");
emitted
.entry(probe_file.clone())
.or_default()
.insert(("LINT-GATE-PROBE".to_string(), "error".to_string()));
let failures = compare(&emitted, &baseline);
assert!(
!failures.is_empty(),
"gate must FAIL when an unbasetined diagnostic is emitted"
);
let named = failures
.iter()
.any(|f| f.contains(&probe_file) && f.contains("LINT-GATE-PROBE"));
assert!(
named,
"gate failure must name the file + code; got:\n - {}",
failures.join("\n - ")
);
}
#[test]
fn build_artifact_paths_are_excluded() {
use std::path::Path;
assert!(is_build_artifact(Path::new(
"/ws/examples/wasm-source-webhook/guest/target/wasm32-wasip2/release/deps/foo.json"
)));
assert!(is_build_artifact(Path::new(
"/ws/crates/components/camel-component-wasm/tests/fixtures/conflicting-bind-guest/target/debug/.fingerprint/x.json"
)));
assert!(!is_build_artifact(Path::new(
"/ws/examples/wasm-source-webhook/routes/webhook.yaml"
)));
assert!(!is_build_artifact(Path::new(
"/ws/crates/camel-cli/tests/fixtures/lint-corpus-baseline.ron"
)));
}
#[tokio::test]
async fn corpus_contains_no_build_artifacts() {
let corpus = discover_corpus();
let leaked: Vec<_> = corpus
.iter()
.filter(|(_, p)| is_build_artifact(p))
.map(|(rel, _)| rel.clone())
.collect();
assert!(
leaked.is_empty(),
"corpus must not contain target/ build artifacts; leaked: {leaked:?}"
);
}