1use std::{
4 collections::{BTreeMap, BTreeSet},
5 sync::Arc,
6};
7
8use sim_kernel::{
9 Claim, ClaimKind, ClaimPattern, Cx, Datum, DatumStore, OpKey, Ref, Result, Symbol,
10 card::{card_kind_predicate, card_tests_predicate},
11 standard::standard_evidence_predicate,
12};
13
14use crate::{FidelityBadge, LanguageProfile, standard_test_capability};
15
16pub type ConformanceCheck =
18 Arc<dyn Fn(&mut Cx, &LanguageProfile) -> Result<ConformanceOutcome> + Send + Sync + 'static>;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum ConformanceStatus {
23 Pass,
25 Fail,
27 Gap,
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct ConformanceOutcome {
35 pub passed: bool,
37 pub detail: Option<String>,
39 pub status: ConformanceStatus,
41}
42
43impl ConformanceOutcome {
44 pub fn pass() -> Self {
46 Self {
47 passed: true,
48 detail: None,
49 status: ConformanceStatus::Pass,
50 }
51 }
52
53 pub fn fail(detail: impl Into<String>) -> Self {
55 Self {
56 passed: false,
57 detail: Some(detail.into()),
58 status: ConformanceStatus::Fail,
59 }
60 }
61
62 pub fn fail_with(detail: impl Into<String>) -> Self {
64 Self::fail(detail)
65 }
66
67 pub fn gap(detail: impl Into<String>) -> Self {
69 Self {
70 passed: false,
71 detail: Some(detail.into()),
72 status: ConformanceStatus::Gap,
73 }
74 }
75
76 pub fn is_pass(&self) -> bool {
78 self.status == ConformanceStatus::Pass
79 }
80
81 pub fn is_fail(&self) -> bool {
83 self.status == ConformanceStatus::Fail
84 }
85
86 pub fn is_gap(&self) -> bool {
88 self.status == ConformanceStatus::Gap
89 }
90
91 pub fn status_symbol(&self) -> Symbol {
93 match self.status {
94 ConformanceStatus::Pass => Symbol::qualified("standard/test", "pass"),
95 ConformanceStatus::Fail => Symbol::qualified("standard/test", "fail"),
96 ConformanceStatus::Gap => Symbol::qualified("standard/test", "gap"),
97 }
98 }
99}
100
101#[derive(Clone)]
104pub struct ConformanceTestCase {
105 pub symbol: Symbol,
107 pub organ: Symbol,
109 pub affected_badge: Option<Symbol>,
111 check: ConformanceCheck,
112}
113
114impl ConformanceTestCase {
115 pub fn new(symbol: Symbol, organ: Symbol, check: ConformanceCheck) -> Self {
117 Self {
118 symbol,
119 organ,
120 affected_badge: None,
121 check,
122 }
123 }
124
125 pub fn affecting_badge(mut self, badge: Symbol) -> Self {
127 self.affected_badge = Some(badge);
128 self
129 }
130
131 fn run(&self, cx: &mut Cx, profile: &LanguageProfile) -> Result<ConformanceOutcome> {
132 (self.check)(cx, profile)
133 }
134}
135
136#[derive(Default)]
138pub struct ConformanceHarness {
139 tests: BTreeMap<Symbol, Vec<ConformanceTestCase>>,
140}
141
142impl ConformanceHarness {
143 pub fn new() -> Self {
145 Self::default()
146 }
147
148 pub fn register_test(&mut self, test: ConformanceTestCase) {
150 self.tests.entry(test.organ.clone()).or_default().push(test);
151 }
152
153 pub fn tests_for_organ(&self, organ: &Symbol) -> &[ConformanceTestCase] {
155 self.tests.get(organ).map(Vec::as_slice).unwrap_or_default()
156 }
157
158 pub fn test_count(&self) -> usize {
160 self.tests.values().map(Vec::len).sum()
161 }
162}
163
164#[derive(Clone, Debug, PartialEq, Eq)]
167pub struct StandardTestReport {
168 pub profile: Symbol,
170 pub organs: Vec<OrganTestReport>,
172 pub reported_badges: Vec<FidelityBadge>,
174}
175
176impl StandardTestReport {
177 pub fn passed(&self) -> bool {
179 self.organs.iter().all(OrganTestReport::passed)
180 }
181
182 pub fn result_count(&self) -> usize {
184 self.organs.iter().map(|organ| organ.tests.len()).sum()
185 }
186}
187
188#[derive(Clone, Debug, PartialEq, Eq)]
190pub struct OrganTestReport {
191 pub organ: Symbol,
193 pub tests: Vec<ConformanceTestReport>,
195}
196
197impl OrganTestReport {
198 pub fn passed(&self) -> bool {
200 self.tests.iter().all(|test| test.passed)
201 }
202}
203
204#[derive(Clone, Debug, PartialEq, Eq)]
206pub struct ConformanceTestReport {
207 pub test: Symbol,
209 pub passed: bool,
211 pub detail: Option<String>,
213 pub evidence: Ref,
215}
216
217pub fn standard_test_op_key() -> OpKey {
219 OpKey::new(Symbol::new("standard"), Symbol::new("test"), 1)
220}
221
222pub fn standard_test_run_kind() -> Symbol {
224 Symbol::qualified("standard", "test-run")
225}
226
227pub fn standard_test_result_predicate() -> Symbol {
229 standard_symbol("test-result")
230}
231
232pub fn standard_test_profile_predicate() -> Symbol {
234 standard_symbol("test-profile")
235}
236
237pub fn standard_test_organ_predicate() -> Symbol {
239 standard_symbol("test-organ")
240}
241
242pub fn standard_test_case_predicate() -> Symbol {
244 standard_symbol("test-case")
245}
246
247pub fn standard_test_status_predicate() -> Symbol {
249 standard_symbol("test-status")
250}
251
252pub fn standard_reported_fidelity_predicate() -> Symbol {
254 standard_symbol("reported-fidelity")
255}
256
257pub fn standard_reported_fidelity_level_predicate() -> Symbol {
259 standard_symbol("reported-fidelity-level")
260}
261
262pub fn standard_test_stub(
269 cx: &mut Cx,
270 harness: &ConformanceHarness,
271 profile: &LanguageProfile,
272) -> Result<StandardTestReport> {
273 cx.require(&standard_test_capability())?;
274 let mut organs = Vec::with_capacity(profile.organs.len());
275 let mut failed_badges = BTreeMap::<Symbol, Ref>::new();
276
277 for organ in &profile.organs {
278 let mut tests = Vec::new();
279 for test in harness.tests_for_organ(&organ.organ) {
280 let outcome = test.run(cx, profile)?;
281 let evidence = publish_test_run(cx, profile, &organ.organ, test, &outcome)?;
282 if outcome.is_fail()
283 && let Some(badge) = &test.affected_badge
284 {
285 failed_badges.insert(badge.clone(), evidence.clone());
286 }
287 tests.push(ConformanceTestReport {
288 test: test.symbol.clone(),
289 passed: outcome.passed,
290 detail: outcome.detail,
291 evidence,
292 });
293 }
294 organs.push(OrganTestReport {
295 organ: organ.organ.clone(),
296 tests,
297 });
298 }
299
300 let reported_badges = lowered_badges(profile, &failed_badges);
301 publish_reported_badges(cx, &reported_badges)?;
302 Ok(StandardTestReport {
303 profile: profile.symbol.clone(),
304 organs,
305 reported_badges,
306 })
307}
308
309fn lowered_badges(
310 profile: &LanguageProfile,
311 failed_badges: &BTreeMap<Symbol, Ref>,
312) -> Vec<FidelityBadge> {
313 profile
314 .fidelity_badges
315 .iter()
316 .map(|badge| {
317 let mut reported = badge.clone();
318 if let Some(evidence) = failed_badges.get(&badge.badge) {
319 reported.level = reported.level.saturating_sub(1);
320 reported.evidence = evidence.clone();
321 }
322 reported
323 })
324 .collect()
325}
326
327fn publish_test_run(
328 cx: &mut Cx,
329 profile: &LanguageProfile,
330 organ: &Symbol,
331 test: &ConformanceTestCase,
332 outcome: &ConformanceOutcome,
333) -> Result<Ref> {
334 let evidence = test_run_ref(cx, profile, organ, test, outcome)?;
335 let status = outcome.status_symbol();
336 insert_observed_once(
337 cx,
338 evidence.clone(),
339 card_kind_predicate(),
340 Ref::Symbol(standard_test_run_kind()),
341 )?;
342 insert_observed_once(
343 cx,
344 evidence.clone(),
345 card_tests_predicate(),
346 Ref::Symbol(test.symbol.clone()),
347 )?;
348 insert_observed_once(
349 cx,
350 evidence.clone(),
351 standard_test_profile_predicate(),
352 Ref::Symbol(profile.symbol.clone()),
353 )?;
354 insert_observed_once(
355 cx,
356 evidence.clone(),
357 standard_test_organ_predicate(),
358 Ref::Symbol(organ.clone()),
359 )?;
360 insert_observed_once(
361 cx,
362 evidence.clone(),
363 standard_test_case_predicate(),
364 Ref::Symbol(test.symbol.clone()),
365 )?;
366 insert_observed_once(
367 cx,
368 evidence.clone(),
369 standard_test_status_predicate(),
370 Ref::Symbol(status),
371 )?;
372 insert_observed_once(
373 cx,
374 Ref::Symbol(profile.symbol.clone()),
375 standard_test_result_predicate(),
376 evidence.clone(),
377 )?;
378 insert_observed_once(
379 cx,
380 Ref::Symbol(organ.clone()),
381 standard_test_result_predicate(),
382 evidence.clone(),
383 )?;
384 insert_observed_once(
385 cx,
386 Ref::Symbol(profile.symbol.clone()),
387 standard_evidence_predicate(),
388 evidence.clone(),
389 )?;
390 Ok(evidence)
391}
392
393fn publish_reported_badges(cx: &mut Cx, badges: &[FidelityBadge]) -> Result<()> {
394 let mut seen = BTreeSet::new();
395 for badge in badges {
396 if !seen.insert((badge.subject.clone(), badge.badge.clone())) {
397 continue;
398 }
399 let evidence = vec![badge.evidence.clone()];
400 insert_observed_with_evidence_once(
401 cx,
402 badge.subject.clone(),
403 standard_reported_fidelity_predicate(),
404 Ref::Symbol(badge.badge.clone()),
405 evidence.clone(),
406 )?;
407 insert_observed_with_evidence_once(
408 cx,
409 badge.subject.clone(),
410 standard_reported_fidelity_level_predicate(),
411 Ref::Symbol(Symbol::qualified(
412 "standard/fidelity-level",
413 badge.level.to_string(),
414 )),
415 evidence,
416 )?;
417 }
418 Ok(())
419}
420
421fn test_run_ref(
422 cx: &mut Cx,
423 profile: &LanguageProfile,
424 organ: &Symbol,
425 test: &ConformanceTestCase,
426 outcome: &ConformanceOutcome,
427) -> Result<Ref> {
428 let mut fields = vec![
429 (
430 Symbol::new("profile"),
431 Datum::Symbol(profile.symbol.clone()),
432 ),
433 (Symbol::new("organ"), Datum::Symbol(organ.clone())),
434 (Symbol::new("test"), Datum::Symbol(test.symbol.clone())),
435 (Symbol::new("passed"), Datum::Bool(outcome.passed)),
436 (
437 Symbol::new("status"),
438 Datum::Symbol(outcome.status_symbol()),
439 ),
440 ];
441 if let Some(detail) = &outcome.detail {
442 fields.push((Symbol::new("detail"), Datum::String(detail.clone())));
443 }
444 cx.datum_store_mut()
445 .intern(Datum::Node {
446 tag: standard_test_run_kind(),
447 fields,
448 })
449 .map(Ref::Content)
450}
451
452fn insert_observed_once(cx: &mut Cx, subject: Ref, predicate: Symbol, object: Ref) -> Result<()> {
453 insert_observed_with_evidence_once(cx, subject, predicate, object, Vec::new())
454}
455
456fn insert_observed_with_evidence_once(
457 cx: &mut Cx,
458 subject: Ref,
459 predicate: Symbol,
460 object: Ref,
461 evidence: Vec<Ref>,
462) -> Result<()> {
463 let exists = !cx
464 .query_facts(ClaimPattern::exact(
465 subject.clone(),
466 predicate.clone(),
467 object.clone(),
468 ))?
469 .is_empty();
470 if !exists {
471 cx.insert_fact(
472 Claim::public(subject, predicate, object)
473 .with_kind(ClaimKind::Observed)
474 .with_evidence(evidence),
475 )?;
476 }
477 Ok(())
478}
479
480fn standard_symbol(name: &str) -> Symbol {
481 Symbol::qualified("standard", name.to_owned())
482}