use std::collections::BTreeMap;
pub(crate) fn corpus_is_entirely_unresolved_dependency(summary: &crate::snippets::types::RunSummary) -> bool {
summary.total > 0 && summary.total == summary.unresolved_dependency
}
pub(crate) fn enforce_snippet_summary(
crate_name: &str,
strict: bool,
summary: &crate::snippets::types::RunSummary,
) -> anyhow::Result<()> {
if summary.checked_nothing() {
if corpus_is_entirely_unresolved_dependency(summary) {
tracing::info!(
total = summary.total,
"docs.snippets for crate `{}` validated {} snippet(s) and NOT ONE reached the requested level -- \
every result is an unresolved dependency on a missing build artifact, the expected shape when \
`alef all`/`alef docs` validate without a preceding `alef build`; run `alef build` then `alef \
snippets check --level compile` (or the configured level) to validate for real",
crate_name,
summary.total
);
} else {
tracing::warn!(
total = summary.total,
"docs.snippets for crate `{}` validated {} snippet(s) and NOT ONE reached the requested level -- \
every result was a failure, a skip, an unavailable environment gap, or capped below what was \
requested; the level this run claims to check was not actually checked anywhere in this corpus",
crate_name,
summary.total
);
}
}
let toolchain_missing = summary.unavailable - summary.unresolved_dependency;
if toolchain_missing > 0 && strict {
anyhow::bail!(
"strict snippet validation failed for crate `{}`: {} unavailable due to a missing toolchain \
(unrelated to a missing build artifact){}",
crate_name,
toolchain_missing,
attribute_unavailable(summary)
);
}
if summary.capability_capped > 0 {
tracing::warn!(
capped = summary.capability_capped,
"docs.snippets validated {} snippet(s) below the requested level because their validator caps lower; \
these pass strict mode, because the level is unreachable for that language rather than degraded{}",
summary.capability_capped,
attribute_capability_capped(summary)
);
}
if summary.declared_capped > 0 {
tracing::warn!(
declared_capped = summary.declared_capped,
"docs.snippets validated {} snippet(s) below the requested level because their own front-matter \
`level:` declares a lower ceiling; these pass strict mode as a satisfied per-snippet contract, \
but the requested level was not actually applied to them{}",
summary.declared_capped,
attribute_declared_capped(summary)
);
}
if summary.preflight_skipped > 0 {
tracing::warn!(
preflight_skipped = summary.preflight_skipped,
total = summary.total,
"docs.snippets skipped {} of {} snippet(s) WITHOUT running a validator: their session's build \
artifacts do not exist, so every one of them would have failed for that single reason. Nothing about \
these snippets was checked. Run `alef build` before validating, or pass --skip-snippet-validation to \
make the generate-only run explicit",
summary.preflight_skipped,
summary.total
);
}
if summary.unavailable > 0 {
let toolchain_missing = summary.unavailable - summary.unresolved_dependency;
if toolchain_missing == 0 {
tracing::info!(
unavailable = summary.unavailable,
unresolved_dependency = summary.unresolved_dependency,
"docs.snippets skipped {} snippet validation(s) because `alef all`/`alef docs` does not build \
first -- every one is an unresolved dependency on a missing build artifact, not a real \
toolchain gap{}",
summary.unavailable,
attribute_unavailable(summary)
);
} else {
tracing::warn!(
unavailable = summary.unavailable,
unresolved_dependency = summary.unresolved_dependency,
toolchain_missing,
"docs.snippets skipped {} snippet validation(s) because required toolchains were unavailable ({} \
unresolved dependency, {} toolchain missing){}",
summary.unavailable,
summary.unresolved_dependency,
toolchain_missing,
attribute_unavailable(summary)
);
}
}
if summary.has_failures() {
anyhow::bail!(
"snippet validation failed for crate `{}`: {} failed, {} errors{}{}{}",
crate_name,
summary.failed,
summary.errors,
timeout_note(summary),
attribute_results(summary, crate::snippets::types::SnippetStatus::Fail),
attribute_results(summary, crate::snippets::types::SnippetStatus::Error)
);
}
if summary.downgraded > 0 && strict {
anyhow::bail!(
"strict snippet validation failed for crate `{}`: {} validation(s) downgraded{}",
crate_name,
summary.downgraded,
attribute_results(summary, crate::snippets::types::SnippetStatus::Downgraded)
);
}
Ok(())
}
fn timeout_note(summary: &crate::snippets::types::RunSummary) -> String {
if summary.timed_out == 0 {
return String::new();
}
format!(
" ({} of them timed out before the toolchain reported on the snippet, so that many measure the timeout \
budget rather than the corpus)",
summary.timed_out
)
}
fn downgrade_reason_label(reason: crate::snippets::types::DowngradeReason) -> &'static str {
use crate::snippets::types::DowngradeReason;
match reason {
DowngradeReason::Declared => "author declared this level via front matter",
DowngradeReason::Annotation => "author suppressed via annotation",
DowngradeReason::ValidatorCapability => "validator cannot reach this level",
DowngradeReason::Environment => "environment could not reach this level",
}
}
pub(crate) fn attribute_results(
summary: &crate::snippets::types::RunSummary,
status: crate::snippets::types::SnippetStatus,
) -> String {
attribute(summary, |result| result.status == status)
}
pub(crate) fn attribute_capability_capped(summary: &crate::snippets::types::RunSummary) -> String {
attribute(summary, |result| result.capability_capped)
}
pub(crate) fn attribute_declared_capped(summary: &crate::snippets::types::RunSummary) -> String {
attribute(summary, |result| {
result.downgrade_reason == Some(crate::snippets::types::DowngradeReason::Declared)
})
}
pub(crate) fn attribute_unavailable(summary: &crate::snippets::types::RunSummary) -> String {
#[derive(Default)]
struct LanguageCounts {
unresolved_dependency: usize,
toolchain_missing: usize,
}
let mut by_language: BTreeMap<String, LanguageCounts> = BTreeMap::new();
for result in summary
.results
.iter()
.filter(|result| result.status == crate::snippets::types::SnippetStatus::Unavailable)
{
let entry = by_language.entry(result.snippet.language.to_string()).or_default();
if result.unresolved_dependency {
entry.unresolved_dependency += 1;
} else {
entry.toolchain_missing += 1;
}
}
if by_language.is_empty() {
return String::new();
}
let mut out = String::new();
for (language, counts) in by_language {
out.push_str(&format!(
"\n {language}: {} unresolved dependency, {} toolchain missing",
counts.unresolved_dependency, counts.toolchain_missing
));
}
out
}
fn attribute(
summary: &crate::snippets::types::RunSummary,
matches: impl Fn(&crate::snippets::types::ValidationResult) -> bool,
) -> String {
const SAMPLE_PER_LANGUAGE: usize = 3;
#[derive(Default)]
struct LanguageGroup {
count: usize,
reasons: BTreeMap<&'static str, usize>,
sample: Vec<String>,
}
let mut by_language: BTreeMap<String, LanguageGroup> = BTreeMap::new();
for result in summary.results.iter().filter(|result| matches(result)) {
let entry = by_language.entry(result.snippet.language.to_string()).or_default();
entry.count += 1;
if let Some(reason) = result.downgrade_reason {
*entry.reasons.entry(downgrade_reason_label(reason)).or_default() += 1;
}
if entry.sample.len() < SAMPLE_PER_LANGUAGE {
let id = result.snippet.id.clone().unwrap_or_else(|| {
format!(
"{}:{}",
result.snippet.source_origin.path.display(),
result.snippet.source_origin.line
)
});
entry.sample.push(format!(
"{id} ({} -> {})",
result.requested_level, result.effective_level
));
}
}
if by_language.is_empty() {
return String::new();
}
let mut out = String::new();
for (language, group) in by_language {
let remainder = group.count.saturating_sub(group.sample.len());
let suffix = if remainder > 0 {
format!(", +{remainder} more")
} else {
String::new()
};
let reasons = group
.reasons
.iter()
.map(|(label, count)| format!("{label}: {count}"))
.collect::<Vec<_>>()
.join(", ");
let reason_suffix = if reasons.is_empty() {
String::new()
} else {
format!(" [{reasons}]")
};
out.push_str(&format!(
"\n {language}: {}{reason_suffix} -- {}{suffix}",
group.count,
group.sample.join(", ")
));
}
out
}