use sim_kernel::{Cx, Result, Symbol, Value};
use sim_lib_standard_core::{ConformanceOutcome, MatrixCellResult, MatrixRunReport};
use crate::{CoverageVerdict, GeneratedCoverageReport};
pub fn coverage_card_fields(
cx: &mut Cx,
report: &GeneratedCoverageReport,
verdict: &CoverageVerdict,
) -> Result<Vec<(Symbol, Value)>> {
let percent = match verdict {
CoverageVerdict::Anchored {
coverage_percent, ..
} => format!("{coverage_percent:.0}%"),
CoverageVerdict::Unanchored { .. } => "unanchored".to_owned(),
};
let citation = match verdict {
CoverageVerdict::Anchored { citation, .. }
| CoverageVerdict::Unanchored { citation, .. } => citation.to_string(),
};
Ok(vec![
(
coverage_generated_field("sampled"),
count_value(cx, report.sampled)?,
),
(
coverage_generated_field("round-trip"),
count_value(cx, report.round_tripped)?,
),
(
coverage_generated_field("percent"),
cx.factory().string(percent)?,
),
(
coverage_generated_field("citation"),
cx.factory().string(citation)?,
),
])
}
pub fn publish_coverage_claims(
cx: &mut Cx,
report: &GeneratedCoverageReport,
verdict: &CoverageVerdict,
) -> Result<()> {
let outcome = match verdict {
CoverageVerdict::Anchored { .. } => ConformanceOutcome::pass(),
CoverageVerdict::Unanchored { reason, .. } => ConformanceOutcome::gap(reason.clone()),
};
MatrixRunReport {
cells: vec![MatrixCellResult {
language: report.language.clone(),
profile: generated_coverage_profile_symbol(&report.language),
organ: generated_coverage_organ_symbol(),
case_symbol: generated_coverage_case_symbol(&report.language),
outcome,
}],
}
.publish_claims(cx)
}
pub fn generated_coverage_profile_symbol(language: &Symbol) -> Symbol {
Symbol::qualified(
"lang/generated",
format!("{}-coverage", language.as_qualified_str()),
)
}
fn coverage_generated_field(name: &str) -> Symbol {
Symbol::new(format!("coverage.generated.{name}"))
}
fn generated_coverage_organ_symbol() -> Symbol {
Symbol::qualified("coverage", "generated")
}
fn generated_coverage_case_symbol(language: &Symbol) -> Symbol {
Symbol::qualified(
"coverage/generated",
format!("{}-run", language.as_qualified_str()),
)
}
fn count_value(cx: &mut Cx, count: usize) -> Result<Value> {
cx.factory()
.number_literal(Symbol::qualified("numbers", "u64"), count.to_string())
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use sim_kernel::{
ClaimKind, ClaimPattern, Cx, DefaultFactory, Expr, NoopEvalPolicy, Ref, Symbol,
};
use sim_lib_standard_core::{
MatrixRunReport, standard_test_capability, standard_test_case_predicate,
standard_test_result_predicate,
};
use super::*;
#[test]
fn coverage_fields_do_not_touch_curated_fidelity() {
let mut cx = test_cx();
let report = anchored_report();
let verdict = anchored_verdict();
let generated = coverage_card_fields(&mut cx, &report, &verdict).unwrap();
let curated = MatrixRunReport::unscored_conformance_card_fields(&mut cx).unwrap();
assert_eq!(
string_field(&mut cx, &generated, "coverage.generated.percent"),
"75%"
);
assert_eq!(
string_field(&mut cx, &curated, "conformance.fidelity"),
"unscored"
);
assert!(missing_field(&generated, "conformance.fidelity"));
assert!(missing_field(&curated, "coverage.generated.percent"));
}
#[test]
fn unanchored_coverage_field_suppresses_percent() {
let mut cx = test_cx();
let report = anchored_report();
let verdict = CoverageVerdict::Unanchored {
citation: Symbol::new("r7rs-small"),
reason: "curated landmark not reproduced".to_owned(),
};
let fields = coverage_card_fields(&mut cx, &report, &verdict).unwrap();
assert_eq!(
string_field(&mut cx, &fields, "coverage.generated.percent"),
"unanchored"
);
assert_eq!(
string_field(&mut cx, &fields, "coverage.generated.citation"),
"r7rs-small"
);
assert_eq!(
number_field(&mut cx, &fields, "coverage.generated.sampled"),
"4"
);
assert_eq!(
number_field(&mut cx, &fields, "coverage.generated.round-trip"),
"3"
);
}
#[test]
fn coverage_claims_use_standard_test_run_evidence() {
let mut cx = test_cx();
cx.grant(standard_test_capability());
let report = anchored_report();
let verdict = anchored_verdict();
publish_coverage_claims(&mut cx, &report, &verdict).unwrap();
let claims = cx
.query_facts(profile_result_claims(&generated_coverage_profile_symbol(
&report.language,
)))
.unwrap();
assert_eq!(claims.len(), 1);
assert_eq!(claims[0].kind, ClaimKind::Observed);
assert!(has_case_claim(
&cx,
&claims[0].object,
generated_coverage_case_symbol(&report.language)
));
}
fn anchored_report() -> GeneratedCoverageReport {
GeneratedCoverageReport {
language: Symbol::new("scheme"),
sampled: 4,
round_tripped: 3,
mismatched: 1,
diagnostics: 0,
max_depth: 2,
seed: vec![Expr::Bool(true)],
landmark_reproduced: true,
unmet_landmarks: Vec::new(),
}
}
fn anchored_verdict() -> CoverageVerdict {
CoverageVerdict::Anchored {
citation: Symbol::new("r7rs-small"),
coverage_percent: 75.0,
curated_fidelity_level: 1,
claim_level: 1,
}
}
fn profile_result_claims(profile: &Symbol) -> ClaimPattern {
ClaimPattern {
subject: Some(Ref::Symbol(profile.clone())),
predicate: Some(standard_test_result_predicate()),
object: None,
include_revoked: false,
}
}
fn has_case_claim(cx: &Cx, evidence: &Ref, case: Symbol) -> bool {
cx.query_facts(ClaimPattern::exact(
evidence.clone(),
standard_test_case_predicate(),
Ref::Symbol(case),
))
.map(|claims| !claims.is_empty())
.unwrap_or(false)
}
fn missing_field(fields: &[(Symbol, Value)], name: &str) -> bool {
fields.iter().all(|(field, _)| field != &Symbol::new(name))
}
fn number_field(cx: &mut Cx, fields: &[(Symbol, Value)], name: &str) -> String {
let Expr::Number(number) = field_expr(cx, fields, name) else {
panic!("expected number field {name}");
};
assert_eq!(number.domain, Symbol::qualified("numbers", "u64"));
number.canonical
}
fn string_field(cx: &mut Cx, fields: &[(Symbol, Value)], name: &str) -> String {
let Expr::String(value) = field_expr(cx, fields, name) else {
panic!("expected string field {name}");
};
value
}
fn field_expr(cx: &mut Cx, fields: &[(Symbol, Value)], name: &str) -> Expr {
fields
.iter()
.find(|(field, _)| field == &Symbol::new(name))
.unwrap_or_else(|| panic!("missing field {name}"))
.1
.object()
.as_expr(cx)
.unwrap()
}
fn test_cx() -> Cx {
Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory))
}
}