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::{
15 CharacterizationScenario, FidelityBadge, LanguageProfile, ScenarioObservationLane,
16 standard_test_capability,
17};
18
19pub type ConformanceCheck =
21 Arc<dyn Fn(&mut Cx, &LanguageProfile) -> Result<ConformanceOutcome> + Send + Sync + 'static>;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum ConformanceStatus {
26 Pass,
28 Fail,
30 Gap,
32}
33
34#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct ConformanceOutcome {
38 pub passed: bool,
40 pub detail: Option<String>,
42 pub status: ConformanceStatus,
44}
45
46impl ConformanceOutcome {
47 pub fn pass() -> Self {
49 Self {
50 passed: true,
51 detail: None,
52 status: ConformanceStatus::Pass,
53 }
54 }
55
56 pub fn fail(detail: impl Into<String>) -> Self {
58 Self {
59 passed: false,
60 detail: Some(detail.into()),
61 status: ConformanceStatus::Fail,
62 }
63 }
64
65 pub fn fail_with(detail: impl Into<String>) -> Self {
67 Self::fail(detail)
68 }
69
70 pub fn gap(detail: impl Into<String>) -> Self {
72 Self {
73 passed: false,
74 detail: Some(detail.into()),
75 status: ConformanceStatus::Gap,
76 }
77 }
78
79 pub fn is_pass(&self) -> bool {
81 self.status == ConformanceStatus::Pass
82 }
83
84 pub fn is_fail(&self) -> bool {
86 self.status == ConformanceStatus::Fail
87 }
88
89 pub fn is_gap(&self) -> bool {
91 self.status == ConformanceStatus::Gap
92 }
93
94 pub fn status_symbol(&self) -> Symbol {
96 match self.status {
97 ConformanceStatus::Pass => Symbol::qualified("standard/test", "pass"),
98 ConformanceStatus::Fail => Symbol::qualified("standard/test", "fail"),
99 ConformanceStatus::Gap => Symbol::qualified("standard/test", "gap"),
100 }
101 }
102}
103
104#[derive(Clone)]
107pub struct ConformanceTestCase {
108 pub symbol: Symbol,
110 pub organ: Symbol,
112 pub affected_badge: Option<Symbol>,
114 check: ConformanceCheck,
115}
116
117impl ConformanceTestCase {
118 pub fn new(symbol: Symbol, organ: Symbol, check: ConformanceCheck) -> Self {
120 Self {
121 symbol,
122 organ,
123 affected_badge: None,
124 check,
125 }
126 }
127
128 pub fn affecting_badge(mut self, badge: Symbol) -> Self {
130 self.affected_badge = Some(badge);
131 self
132 }
133
134 fn run(&self, cx: &mut Cx, profile: &LanguageProfile) -> Result<ConformanceOutcome> {
135 (self.check)(cx, profile)
136 }
137}
138
139#[derive(Default)]
141pub struct ConformanceHarness {
142 tests: BTreeMap<Symbol, Vec<ConformanceTestCase>>,
143 scenarios: BTreeMap<Symbol, CharacterizationScenario>,
144 supported_scenario_lanes: BTreeSet<ScenarioObservationLane>,
145}
146
147impl ConformanceHarness {
148 pub fn new() -> Self {
150 Self {
151 tests: BTreeMap::new(),
152 scenarios: BTreeMap::new(),
153 supported_scenario_lanes: BTreeSet::from([
154 ScenarioObservationLane::ValueOrFailure,
155 ScenarioObservationLane::Events,
156 ScenarioObservationLane::Receipts,
157 ScenarioObservationLane::Browse,
158 ]),
159 }
160 }
161
162 pub fn register_test(&mut self, test: ConformanceTestCase) {
164 self.tests.entry(test.organ.clone()).or_default().push(test);
165 }
166
167 pub fn tests_for_organ(&self, organ: &Symbol) -> &[ConformanceTestCase] {
169 self.tests.get(organ).map(Vec::as_slice).unwrap_or_default()
170 }
171
172 pub fn test_count(&self) -> usize {
174 self.tests.values().map(Vec::len).sum()
175 }
176
177 pub fn with_supported_scenario_lanes(
179 mut self,
180 lanes: impl IntoIterator<Item = ScenarioObservationLane>,
181 ) -> Self {
182 self.supported_scenario_lanes = lanes.into_iter().collect();
183 self
184 }
185
186 pub fn register_scenario(&mut self, scenario: CharacterizationScenario) -> Result<()> {
188 if self.scenarios.contains_key(&scenario.spec.id) {
189 return Err(sim_kernel::Error::Eval(format!(
190 "duplicate scenario id {}",
191 scenario.spec.id
192 )));
193 }
194 self.scenarios.insert(scenario.spec.id.clone(), scenario);
195 Ok(())
196 }
197
198 pub fn run_scenarios(&self, cx: &mut Cx) -> Result<Vec<Symbol>> {
202 for scenario in self.scenarios.values() {
203 scenario.spec.validate(&self.supported_scenario_lanes)?;
204 }
205 let mut completed = Vec::with_capacity(self.scenarios.len());
206 for scenario in self.scenarios.values() {
207 (scenario.driver)(cx, &scenario.spec)?;
208 completed.push(scenario.spec.id.clone());
209 }
210 Ok(completed)
211 }
212}
213
214#[derive(Clone, Debug, PartialEq, Eq)]
217pub struct StandardTestReport {
218 pub profile: Symbol,
220 pub organs: Vec<OrganTestReport>,
222 pub reported_badges: Vec<FidelityBadge>,
224}
225
226impl StandardTestReport {
227 pub fn passed(&self) -> bool {
229 self.organs.iter().all(OrganTestReport::passed)
230 }
231
232 pub fn result_count(&self) -> usize {
234 self.organs.iter().map(|organ| organ.tests.len()).sum()
235 }
236}
237
238#[derive(Clone, Debug, PartialEq, Eq)]
240pub struct OrganTestReport {
241 pub organ: Symbol,
243 pub tests: Vec<ConformanceTestReport>,
245}
246
247impl OrganTestReport {
248 pub fn passed(&self) -> bool {
250 self.tests.iter().all(|test| test.passed)
251 }
252}
253
254#[derive(Clone, Debug, PartialEq, Eq)]
256pub struct ConformanceTestReport {
257 pub test: Symbol,
259 pub passed: bool,
261 pub detail: Option<String>,
263 pub evidence: Ref,
265}
266
267pub fn standard_test_op_key() -> OpKey {
269 OpKey::new(Symbol::new("standard"), Symbol::new("test"), 1)
270}
271
272pub fn standard_test_run_kind() -> Symbol {
274 Symbol::qualified("standard", "test-run")
275}
276
277pub fn standard_test_result_predicate() -> Symbol {
279 standard_symbol("test-result")
280}
281
282pub fn standard_test_profile_predicate() -> Symbol {
284 standard_symbol("test-profile")
285}
286
287pub fn standard_test_organ_predicate() -> Symbol {
289 standard_symbol("test-organ")
290}
291
292pub fn standard_test_case_predicate() -> Symbol {
294 standard_symbol("test-case")
295}
296
297pub fn standard_test_status_predicate() -> Symbol {
299 standard_symbol("test-status")
300}
301
302pub fn standard_reported_fidelity_predicate() -> Symbol {
304 standard_symbol("reported-fidelity")
305}
306
307pub fn standard_reported_fidelity_level_predicate() -> Symbol {
309 standard_symbol("reported-fidelity-level")
310}
311
312pub fn standard_test_stub(
319 cx: &mut Cx,
320 harness: &ConformanceHarness,
321 profile: &LanguageProfile,
322) -> Result<StandardTestReport> {
323 cx.require(&standard_test_capability())?;
324 let mut organs = Vec::with_capacity(profile.organs.len());
325 let mut failed_badges = BTreeMap::<Symbol, Ref>::new();
326
327 for organ in &profile.organs {
328 let mut tests = Vec::new();
329 for test in harness.tests_for_organ(&organ.organ) {
330 let outcome = test.run(cx, profile)?;
331 let evidence = publish_test_run(cx, profile, &organ.organ, test, &outcome)?;
332 if outcome.is_fail()
333 && let Some(badge) = &test.affected_badge
334 {
335 failed_badges.insert(badge.clone(), evidence.clone());
336 }
337 tests.push(ConformanceTestReport {
338 test: test.symbol.clone(),
339 passed: outcome.passed,
340 detail: outcome.detail,
341 evidence,
342 });
343 }
344 organs.push(OrganTestReport {
345 organ: organ.organ.clone(),
346 tests,
347 });
348 }
349
350 let reported_badges = lowered_badges(profile, &failed_badges);
351 publish_reported_badges(cx, &reported_badges)?;
352 Ok(StandardTestReport {
353 profile: profile.symbol.clone(),
354 organs,
355 reported_badges,
356 })
357}
358
359fn lowered_badges(
360 profile: &LanguageProfile,
361 failed_badges: &BTreeMap<Symbol, Ref>,
362) -> Vec<FidelityBadge> {
363 profile
364 .fidelity_badges
365 .iter()
366 .map(|badge| {
367 let mut reported = badge.clone();
368 if let Some(evidence) = failed_badges.get(&badge.badge) {
369 reported.level = reported.level.saturating_sub(1);
370 reported.evidence = evidence.clone();
371 }
372 reported
373 })
374 .collect()
375}
376
377fn publish_test_run(
378 cx: &mut Cx,
379 profile: &LanguageProfile,
380 organ: &Symbol,
381 test: &ConformanceTestCase,
382 outcome: &ConformanceOutcome,
383) -> Result<Ref> {
384 let evidence = test_run_ref(cx, profile, organ, test, outcome)?;
385 let status = outcome.status_symbol();
386 insert_observed_once(
387 cx,
388 evidence.clone(),
389 card_kind_predicate(),
390 Ref::Symbol(standard_test_run_kind()),
391 )?;
392 insert_observed_once(
393 cx,
394 evidence.clone(),
395 card_tests_predicate(),
396 Ref::Symbol(test.symbol.clone()),
397 )?;
398 insert_observed_once(
399 cx,
400 evidence.clone(),
401 standard_test_profile_predicate(),
402 Ref::Symbol(profile.symbol.clone()),
403 )?;
404 insert_observed_once(
405 cx,
406 evidence.clone(),
407 standard_test_organ_predicate(),
408 Ref::Symbol(organ.clone()),
409 )?;
410 insert_observed_once(
411 cx,
412 evidence.clone(),
413 standard_test_case_predicate(),
414 Ref::Symbol(test.symbol.clone()),
415 )?;
416 insert_observed_once(
417 cx,
418 evidence.clone(),
419 standard_test_status_predicate(),
420 Ref::Symbol(status),
421 )?;
422 insert_observed_once(
423 cx,
424 Ref::Symbol(profile.symbol.clone()),
425 standard_test_result_predicate(),
426 evidence.clone(),
427 )?;
428 insert_observed_once(
429 cx,
430 Ref::Symbol(organ.clone()),
431 standard_test_result_predicate(),
432 evidence.clone(),
433 )?;
434 insert_observed_once(
435 cx,
436 Ref::Symbol(profile.symbol.clone()),
437 standard_evidence_predicate(),
438 evidence.clone(),
439 )?;
440 Ok(evidence)
441}
442
443fn publish_reported_badges(cx: &mut Cx, badges: &[FidelityBadge]) -> Result<()> {
444 let mut seen = BTreeSet::new();
445 for badge in badges {
446 if !seen.insert((badge.subject.clone(), badge.badge.clone())) {
447 continue;
448 }
449 let evidence = vec![badge.evidence.clone()];
450 insert_observed_with_evidence_once(
451 cx,
452 badge.subject.clone(),
453 standard_reported_fidelity_predicate(),
454 Ref::Symbol(badge.badge.clone()),
455 evidence.clone(),
456 )?;
457 insert_observed_with_evidence_once(
458 cx,
459 badge.subject.clone(),
460 standard_reported_fidelity_level_predicate(),
461 Ref::Symbol(Symbol::qualified(
462 "standard/fidelity-level",
463 badge.level.to_string(),
464 )),
465 evidence,
466 )?;
467 }
468 Ok(())
469}
470
471fn test_run_ref(
472 cx: &mut Cx,
473 profile: &LanguageProfile,
474 organ: &Symbol,
475 test: &ConformanceTestCase,
476 outcome: &ConformanceOutcome,
477) -> Result<Ref> {
478 let mut fields = vec![
479 (
480 Symbol::new("profile"),
481 Datum::Symbol(profile.symbol.clone()),
482 ),
483 (Symbol::new("organ"), Datum::Symbol(organ.clone())),
484 (Symbol::new("test"), Datum::Symbol(test.symbol.clone())),
485 (Symbol::new("passed"), Datum::Bool(outcome.passed)),
486 (
487 Symbol::new("status"),
488 Datum::Symbol(outcome.status_symbol()),
489 ),
490 ];
491 if let Some(detail) = &outcome.detail {
492 fields.push((Symbol::new("detail"), Datum::String(detail.clone())));
493 }
494 cx.datum_store_mut()
495 .intern(Datum::Node {
496 tag: standard_test_run_kind(),
497 fields,
498 })
499 .map(Ref::Content)
500}
501
502fn insert_observed_once(cx: &mut Cx, subject: Ref, predicate: Symbol, object: Ref) -> Result<()> {
503 insert_observed_with_evidence_once(cx, subject, predicate, object, Vec::new())
504}
505
506fn insert_observed_with_evidence_once(
507 cx: &mut Cx,
508 subject: Ref,
509 predicate: Symbol,
510 object: Ref,
511 evidence: Vec<Ref>,
512) -> Result<()> {
513 let exists = !cx
514 .query_facts(ClaimPattern::exact(
515 subject.clone(),
516 predicate.clone(),
517 object.clone(),
518 ))?
519 .is_empty();
520 if !exists {
521 cx.insert_fact(
522 Claim::public(subject, predicate, object)
523 .with_kind(ClaimKind::Observed)
524 .with_evidence(evidence),
525 )?;
526 }
527 Ok(())
528}
529
530fn standard_symbol(name: &str) -> Symbol {
531 Symbol::qualified("standard", name.to_owned())
532}