use std::{
collections::{BTreeMap, BTreeSet},
sync::Arc,
};
use sim_kernel::{
Claim, ClaimKind, ClaimPattern, Cx, Datum, DatumStore, OpKey, Ref, Result, Symbol,
card::{card_kind_predicate, card_tests_predicate},
standard::standard_evidence_predicate,
};
use crate::{FidelityBadge, LanguageProfile, standard_test_capability};
pub type ConformanceCheck =
Arc<dyn Fn(&mut Cx, &LanguageProfile) -> Result<ConformanceOutcome> + Send + Sync + 'static>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConformanceStatus {
Pass,
Fail,
Gap,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConformanceOutcome {
pub passed: bool,
pub detail: Option<String>,
pub status: ConformanceStatus,
}
impl ConformanceOutcome {
pub fn pass() -> Self {
Self {
passed: true,
detail: None,
status: ConformanceStatus::Pass,
}
}
pub fn fail(detail: impl Into<String>) -> Self {
Self {
passed: false,
detail: Some(detail.into()),
status: ConformanceStatus::Fail,
}
}
pub fn fail_with(detail: impl Into<String>) -> Self {
Self::fail(detail)
}
pub fn gap(detail: impl Into<String>) -> Self {
Self {
passed: false,
detail: Some(detail.into()),
status: ConformanceStatus::Gap,
}
}
pub fn is_pass(&self) -> bool {
self.status == ConformanceStatus::Pass
}
pub fn is_fail(&self) -> bool {
self.status == ConformanceStatus::Fail
}
pub fn is_gap(&self) -> bool {
self.status == ConformanceStatus::Gap
}
pub fn status_symbol(&self) -> Symbol {
match self.status {
ConformanceStatus::Pass => Symbol::qualified("standard/test", "pass"),
ConformanceStatus::Fail => Symbol::qualified("standard/test", "fail"),
ConformanceStatus::Gap => Symbol::qualified("standard/test", "gap"),
}
}
}
#[derive(Clone)]
pub struct ConformanceTestCase {
pub symbol: Symbol,
pub organ: Symbol,
pub affected_badge: Option<Symbol>,
check: ConformanceCheck,
}
impl ConformanceTestCase {
pub fn new(symbol: Symbol, organ: Symbol, check: ConformanceCheck) -> Self {
Self {
symbol,
organ,
affected_badge: None,
check,
}
}
pub fn affecting_badge(mut self, badge: Symbol) -> Self {
self.affected_badge = Some(badge);
self
}
fn run(&self, cx: &mut Cx, profile: &LanguageProfile) -> Result<ConformanceOutcome> {
(self.check)(cx, profile)
}
}
#[derive(Default)]
pub struct ConformanceHarness {
tests: BTreeMap<Symbol, Vec<ConformanceTestCase>>,
}
impl ConformanceHarness {
pub fn new() -> Self {
Self::default()
}
pub fn register_test(&mut self, test: ConformanceTestCase) {
self.tests.entry(test.organ.clone()).or_default().push(test);
}
pub fn tests_for_organ(&self, organ: &Symbol) -> &[ConformanceTestCase] {
self.tests.get(organ).map(Vec::as_slice).unwrap_or_default()
}
pub fn test_count(&self) -> usize {
self.tests.values().map(Vec::len).sum()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StandardTestReport {
pub profile: Symbol,
pub organs: Vec<OrganTestReport>,
pub reported_badges: Vec<FidelityBadge>,
}
impl StandardTestReport {
pub fn passed(&self) -> bool {
self.organs.iter().all(OrganTestReport::passed)
}
pub fn result_count(&self) -> usize {
self.organs.iter().map(|organ| organ.tests.len()).sum()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OrganTestReport {
pub organ: Symbol,
pub tests: Vec<ConformanceTestReport>,
}
impl OrganTestReport {
pub fn passed(&self) -> bool {
self.tests.iter().all(|test| test.passed)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConformanceTestReport {
pub test: Symbol,
pub passed: bool,
pub detail: Option<String>,
pub evidence: Ref,
}
pub fn standard_test_op_key() -> OpKey {
OpKey::new(Symbol::new("standard"), Symbol::new("test"), 1)
}
pub fn standard_test_run_kind() -> Symbol {
Symbol::qualified("standard", "test-run")
}
pub fn standard_test_result_predicate() -> Symbol {
standard_symbol("test-result")
}
pub fn standard_test_profile_predicate() -> Symbol {
standard_symbol("test-profile")
}
pub fn standard_test_organ_predicate() -> Symbol {
standard_symbol("test-organ")
}
pub fn standard_test_case_predicate() -> Symbol {
standard_symbol("test-case")
}
pub fn standard_test_status_predicate() -> Symbol {
standard_symbol("test-status")
}
pub fn standard_reported_fidelity_predicate() -> Symbol {
standard_symbol("reported-fidelity")
}
pub fn standard_reported_fidelity_level_predicate() -> Symbol {
standard_symbol("reported-fidelity-level")
}
pub fn standard_test_stub(
cx: &mut Cx,
harness: &ConformanceHarness,
profile: &LanguageProfile,
) -> Result<StandardTestReport> {
cx.require(&standard_test_capability())?;
let mut organs = Vec::with_capacity(profile.organs.len());
let mut failed_badges = BTreeMap::<Symbol, Ref>::new();
for organ in &profile.organs {
let mut tests = Vec::new();
for test in harness.tests_for_organ(&organ.organ) {
let outcome = test.run(cx, profile)?;
let evidence = publish_test_run(cx, profile, &organ.organ, test, &outcome)?;
if outcome.is_fail()
&& let Some(badge) = &test.affected_badge
{
failed_badges.insert(badge.clone(), evidence.clone());
}
tests.push(ConformanceTestReport {
test: test.symbol.clone(),
passed: outcome.passed,
detail: outcome.detail,
evidence,
});
}
organs.push(OrganTestReport {
organ: organ.organ.clone(),
tests,
});
}
let reported_badges = lowered_badges(profile, &failed_badges);
publish_reported_badges(cx, &reported_badges)?;
Ok(StandardTestReport {
profile: profile.symbol.clone(),
organs,
reported_badges,
})
}
fn lowered_badges(
profile: &LanguageProfile,
failed_badges: &BTreeMap<Symbol, Ref>,
) -> Vec<FidelityBadge> {
profile
.fidelity_badges
.iter()
.map(|badge| {
let mut reported = badge.clone();
if let Some(evidence) = failed_badges.get(&badge.badge) {
reported.level = reported.level.saturating_sub(1);
reported.evidence = evidence.clone();
}
reported
})
.collect()
}
fn publish_test_run(
cx: &mut Cx,
profile: &LanguageProfile,
organ: &Symbol,
test: &ConformanceTestCase,
outcome: &ConformanceOutcome,
) -> Result<Ref> {
let evidence = test_run_ref(cx, profile, organ, test, outcome)?;
let status = outcome.status_symbol();
insert_observed_once(
cx,
evidence.clone(),
card_kind_predicate(),
Ref::Symbol(standard_test_run_kind()),
)?;
insert_observed_once(
cx,
evidence.clone(),
card_tests_predicate(),
Ref::Symbol(test.symbol.clone()),
)?;
insert_observed_once(
cx,
evidence.clone(),
standard_test_profile_predicate(),
Ref::Symbol(profile.symbol.clone()),
)?;
insert_observed_once(
cx,
evidence.clone(),
standard_test_organ_predicate(),
Ref::Symbol(organ.clone()),
)?;
insert_observed_once(
cx,
evidence.clone(),
standard_test_case_predicate(),
Ref::Symbol(test.symbol.clone()),
)?;
insert_observed_once(
cx,
evidence.clone(),
standard_test_status_predicate(),
Ref::Symbol(status),
)?;
insert_observed_once(
cx,
Ref::Symbol(profile.symbol.clone()),
standard_test_result_predicate(),
evidence.clone(),
)?;
insert_observed_once(
cx,
Ref::Symbol(organ.clone()),
standard_test_result_predicate(),
evidence.clone(),
)?;
insert_observed_once(
cx,
Ref::Symbol(profile.symbol.clone()),
standard_evidence_predicate(),
evidence.clone(),
)?;
Ok(evidence)
}
fn publish_reported_badges(cx: &mut Cx, badges: &[FidelityBadge]) -> Result<()> {
let mut seen = BTreeSet::new();
for badge in badges {
if !seen.insert((badge.subject.clone(), badge.badge.clone())) {
continue;
}
let evidence = vec![badge.evidence.clone()];
insert_observed_with_evidence_once(
cx,
badge.subject.clone(),
standard_reported_fidelity_predicate(),
Ref::Symbol(badge.badge.clone()),
evidence.clone(),
)?;
insert_observed_with_evidence_once(
cx,
badge.subject.clone(),
standard_reported_fidelity_level_predicate(),
Ref::Symbol(Symbol::qualified(
"standard/fidelity-level",
badge.level.to_string(),
)),
evidence,
)?;
}
Ok(())
}
fn test_run_ref(
cx: &mut Cx,
profile: &LanguageProfile,
organ: &Symbol,
test: &ConformanceTestCase,
outcome: &ConformanceOutcome,
) -> Result<Ref> {
let mut fields = vec![
(
Symbol::new("profile"),
Datum::Symbol(profile.symbol.clone()),
),
(Symbol::new("organ"), Datum::Symbol(organ.clone())),
(Symbol::new("test"), Datum::Symbol(test.symbol.clone())),
(Symbol::new("passed"), Datum::Bool(outcome.passed)),
(
Symbol::new("status"),
Datum::Symbol(outcome.status_symbol()),
),
];
if let Some(detail) = &outcome.detail {
fields.push((Symbol::new("detail"), Datum::String(detail.clone())));
}
cx.datum_store_mut()
.intern(Datum::Node {
tag: standard_test_run_kind(),
fields,
})
.map(Ref::Content)
}
fn insert_observed_once(cx: &mut Cx, subject: Ref, predicate: Symbol, object: Ref) -> Result<()> {
insert_observed_with_evidence_once(cx, subject, predicate, object, Vec::new())
}
fn insert_observed_with_evidence_once(
cx: &mut Cx,
subject: Ref,
predicate: Symbol,
object: Ref,
evidence: Vec<Ref>,
) -> Result<()> {
let exists = !cx
.query_facts(ClaimPattern::exact(
subject.clone(),
predicate.clone(),
object.clone(),
))?
.is_empty();
if !exists {
cx.insert_fact(
Claim::public(subject, predicate, object)
.with_kind(ClaimKind::Observed)
.with_evidence(evidence),
)?;
}
Ok(())
}
fn standard_symbol(name: &str) -> Symbol {
Symbol::qualified("standard", name.to_owned())
}