use std::collections::HashSet;
use std::sync::{Arc, Mutex, PoisonError};
use super::validate::ValidationError;
#[derive(Clone, Debug, Default)]
pub struct DiagnosticLog {
reported: Arc<Mutex<HashSet<(String, String)>>>,
}
impl DiagnosticLog {
pub fn new() -> Self {
Self::default()
}
pub fn is_first_occurrence(&self, file: &str, message: &str) -> bool {
let mut reported = self.reported.lock().unwrap_or_else(PoisonError::into_inner);
reported.insert((file.to_owned(), message.to_owned()))
}
}
pub fn unreported<'a>(diagnostics: &'a [ValidationError], log: &DiagnosticLog) -> Vec<&'a ValidationError> {
diagnostics
.iter()
.filter(|diagnostic| log.is_first_occurrence(&diagnostic.file, &diagnostic.message))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::e2e::validate::Severity;
fn diagnostic(file: &str, message: &str) -> ValidationError {
ValidationError {
file: file.to_owned(),
message: message.to_owned(),
severity: Severity::Warning,
}
}
#[test]
fn a_second_identical_pass_reports_nothing_new() {
let diagnostics = vec![
diagnostic("calls.toml", "module 'x' is not importable"),
diagnostic("fixtures/a.json", "unknown arg 'y'"),
];
let log = DiagnosticLog::new();
let first = unreported(&diagnostics, &log);
let second = unreported(&diagnostics, &log);
assert_eq!(first.len(), 2);
assert_eq!(
second.len(),
0,
"the registry pass must not repeat the local pass's diagnostics"
);
}
#[test]
fn deduplication_preserves_every_distinct_diagnostic() {
let diagnostics = vec![
diagnostic("calls.toml", "module 'x' is not importable"),
diagnostic("fixtures/a.json", "unknown arg 'y'"),
];
let log = DiagnosticLog::new();
let mut reported: Vec<(String, String)> = unreported(&diagnostics, &log)
.into_iter()
.chain(unreported(&diagnostics, &log))
.map(|diagnostic| (diagnostic.file.clone(), diagnostic.message.clone()))
.collect();
reported.sort();
assert_eq!(
reported,
vec![
("calls.toml".to_owned(), "module 'x' is not importable".to_owned()),
("fixtures/a.json".to_owned(), "unknown arg 'y'".to_owned()),
]
);
}
#[test]
fn diagnostics_differing_only_in_file_are_both_reported() {
let diagnostics = vec![
diagnostic("crate-a/alef.toml", "module 'x' is not importable"),
diagnostic("crate-b/alef.toml", "module 'x' is not importable"),
];
let log = DiagnosticLog::new();
assert_eq!(unreported(&diagnostics, &log).len(), 2);
}
#[test]
fn a_fresh_log_reports_a_previously_reported_diagnostic_again() {
let diagnostics = vec![diagnostic("calls.toml", "module 'x' is not importable")];
let first_invocation = DiagnosticLog::new();
let _ = unreported(&diagnostics, &first_invocation);
let second_invocation = DiagnosticLog::new();
assert_eq!(unreported(&diagnostics, &second_invocation).len(), 1);
}
#[test]
fn a_clone_shares_the_record_with_its_original() {
let diagnostics = vec![diagnostic("calls.toml", "module 'x' is not importable")];
let log = DiagnosticLog::new();
let shared = log.clone();
let _ = unreported(&diagnostics, &log);
assert_eq!(
unreported(&diagnostics, &shared).len(),
0,
"a clone must share suppression so parallel stages agree"
);
}
}