1use std::borrow::Cow;
107use std::cell::RefCell;
108use std::fmt;
109
110use crate::descriptor::Descriptor;
111use crate::error::ErrorCode;
112use crate::keys::Keys;
113use crate::model::{Cell, Scalar};
114use crate::receipt::Receipt;
115use crate::slice::Manifest;
116use crate::verify::{verify, Evaluation, Level, Resolver, VerifyResult};
117
118#[rustfmt::skip]
119mod data;
120
121#[non_exhaustive]
130#[derive(Clone, Debug, PartialEq, Eq)]
131pub struct Fixtures {
132 pub key_documents: Vec<(String, Vec<u8>)>,
135 pub descriptors: Vec<(String, Vec<u8>)>,
137 pub receipts: Vec<(String, Vec<u8>)>,
139 pub versions: Vec<Version>,
141 pub slices: SliceFixtures,
143}
144
145impl Fixtures {
146 pub fn keys(&self) -> Keys {
148 keys_of(&self.key_documents)
149 }
150}
151
152fn keys_of(docs: &[(String, Vec<u8>)]) -> Keys {
153 let mut keys = Keys::new();
154 for (host, body) in docs {
155 keys.insert_response(host, 200, Some(body));
156 }
157 keys
158}
159
160#[non_exhaustive]
165#[derive(Clone, Debug, PartialEq, Eq)]
166pub struct SliceFixtures {
167 pub key_documents: Vec<(String, Vec<u8>)>,
169 pub descriptors: Vec<(String, Vec<u8>)>,
171 pub receipts: Vec<(String, Vec<u8>)>,
173 pub manifests: Vec<(String, Vec<u8>)>,
175}
176
177impl SliceFixtures {
178 pub fn keys(&self) -> Keys {
180 keys_of(&self.key_documents)
181 }
182}
183
184#[non_exhaustive]
188#[derive(Clone, Debug, PartialEq, Eq)]
189pub struct Version {
190 pub cell_id: String,
192 pub tx: String,
194 pub receipt_hash: String,
196 pub value: Scalar,
198}
199
200#[non_exhaustive]
205#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
206pub enum AnswerRule {
207 #[default]
211 TxAndAsof,
212 TxOnly,
215}
216
217#[non_exhaustive]
223#[derive(Clone, Debug, PartialEq, Eq)]
224pub enum Call {
225 LookupDescriptor(String),
227 LookupReceipt(String),
229 LookupManifest(String),
231 Evaluate {
234 cell_id: String,
236 asof: String,
238 },
239}
240
241pub struct Recording<'a> {
243 inner: &'a dyn Resolver,
244 calls: RefCell<Vec<Call>>,
245}
246
247impl<'a> Recording<'a> {
248 pub fn new(inner: &'a dyn Resolver) -> Recording<'a> {
250 Recording { inner, calls: RefCell::new(Vec::new()) }
251 }
252
253 pub fn calls(&self) -> Vec<Call> {
255 self.calls.borrow().clone()
256 }
257
258 pub fn take(&self) -> Vec<Call> {
260 self.calls.take()
261 }
262}
263
264impl fmt::Debug for Recording<'_> {
265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266 f.debug_struct("Recording").field("calls", &self.calls.borrow()).finish_non_exhaustive()
267 }
268}
269
270impl Resolver for Recording<'_> {
271 fn descriptor(&self, fn_hash: &str) -> Option<Cow<'_, [u8]>> {
272 self.calls.borrow_mut().push(Call::LookupDescriptor(fn_hash.to_string()));
273 self.inner.descriptor(fn_hash)
274 }
275
276 fn receipt(&self, hash: &str) -> Option<Cow<'_, [u8]>> {
277 self.calls.borrow_mut().push(Call::LookupReceipt(hash.to_string()));
278 self.inner.receipt(hash)
279 }
280
281 fn evaluate(&self, cell: &Cell, asof: &str) -> Option<Scalar> {
282 self.calls.borrow_mut().push(Call::Evaluate { cell_id: cell.id(), asof: asof.to_string() });
283 self.inner.evaluate(cell, asof)
284 }
285
286 fn manifest(&self, entry: &str) -> Option<Cow<'_, [u8]>> {
287 self.calls.borrow_mut().push(Call::LookupManifest(entry.to_string()));
288 self.inner.manifest(entry)
289 }
290
291 fn evaluation(&self, cell: &Cell, asof: &str) -> Option<Evaluation> {
292 self.calls.borrow_mut().push(Call::Evaluate { cell_id: cell.id(), asof: asof.to_string() });
293 self.inner.evaluation(cell, asof)
294 }
295}
296
297#[derive(Clone, Debug, PartialEq, Eq)]
301pub struct Report {
302 failures: Vec<String>,
303}
304
305impl Report {
306 pub fn failures(&self) -> &[String] {
308 &self.failures
309 }
310}
311
312impl fmt::Display for Report {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 for (i, line) in self.failures.iter().enumerate() {
315 if i > 0 {
316 f.write_str("\n")?;
317 }
318 f.write_str(line)?;
319 }
320 Ok(())
321 }
322}
323
324impl std::error::Error for Report {}
325
326#[derive(Clone, Debug)]
328struct Case {
329 name: &'static str,
330 bytes: Vec<u8>,
331}
332
333#[derive(Clone, Debug)]
335pub struct Kit {
336 fixtures: Fixtures,
337 cases: Vec<Case>,
338 slice_cases: Vec<Case>,
339 time_aware: bool,
340 answer_rule: AnswerRule,
341 slices: bool,
342 setup: Vec<String>,
343}
344
345impl Default for Kit {
346 fn default() -> Kit {
347 Kit::new()
348 }
349}
350
351const NOT_JSON: &str = "not-json";
353
354impl Kit {
355 pub fn new() -> Kit {
358 let mut setup = Vec::new();
359 let mut fx = Fixtures {
360 key_documents: Vec::new(),
361 descriptors: Vec::new(),
362 receipts: Vec::new(),
363 versions: Vec::new(),
364 slices: SliceFixtures {
365 key_documents: Vec::new(),
366 descriptors: Vec::new(),
367 receipts: Vec::new(),
368 manifests: Vec::new(),
369 },
370 };
371 let mut cases = Vec::new();
372 for (host, text) in data::KEY_DOCUMENTS {
373 fx.key_documents.push((host.to_string(), text.as_bytes().to_vec()));
374 }
375 let mut stored = Vec::new();
376 for (source, text) in data::DESCRIPTORS {
377 match Descriptor::from_json(text.as_bytes()) {
378 Ok(d) => {
379 if d.lang == "stored" {
380 stored.push(d.hash());
381 }
382 fx.descriptors.push((d.hash(), text.as_bytes().to_vec()));
383 }
384 Err(e) => setup.push(format!("setup: {source}: {e}")),
385 }
386 }
387 for (name, source, text) in data::RECEIPTS {
388 match Receipt::from_json(text.as_bytes()) {
389 Ok(r) => {
390 if stored.contains(&r.cell().fn_hash) {
391 fx.versions.push(Version {
392 cell_id: r.cell().id(),
393 tx: r.tx().to_string(),
394 receipt_hash: r.hash().to_string(),
395 value: r.value().clone(),
396 });
397 }
398 fx.receipts.push((r.hash().to_string(), text.as_bytes().to_vec()));
399 }
400 Err(e) => setup.push(format!("setup: {source}: {e}")),
401 }
402 cases.push(Case { name, bytes: text.as_bytes().to_vec() });
403 }
404 for (name, _, text) in data::PROBES {
405 cases.push(Case { name, bytes: text.as_bytes().to_vec() });
406 }
407 cases.push(Case { name: NOT_JSON, bytes: b"{".to_vec() });
408 fx.versions.sort_by(|a, b| a.cell_id.cmp(&b.cell_id).then_with(|| a.tx.cmp(&b.tx)));
410 let slice_cases = load_slices(&mut fx.slices, &mut setup);
411 Kit {
412 fixtures: fx,
413 cases,
414 slice_cases,
415 time_aware: false,
416 answer_rule: AnswerRule::default(),
417 slices: false,
418 setup,
419 }
420 }
421
422 pub fn slices(mut self, yes: bool) -> Kit {
427 self.slices = yes;
428 self
429 }
430
431 pub fn time_aware(mut self, yes: bool) -> Kit {
438 self.time_aware = yes;
439 self
440 }
441
442 pub fn answer_rule(mut self, rule: AnswerRule) -> Kit {
446 self.answer_rule = rule;
447 self
448 }
449
450 pub fn fixtures(&self) -> &Fixtures {
452 &self.fixtures
453 }
454
455 pub fn run<R: Resolver, F: FnOnce(&Fixtures) -> R>(&self, load: F) -> Result<(), Report> {
457 self.setup_ok()?;
458 let r = load(&self.fixtures);
459 self.check(&r)
460 }
461
462 pub fn check(&self, resolver: &dyn Resolver) -> Result<(), Report> {
464 self.setup_ok()?;
465 let mut failures = Vec::new();
466 let mut checks: Vec<Check> =
467 vec![Kit::check_lazy_calls, Kit::check_unavailable, Kit::check_bytes, Kit::check_asof];
468 if self.slices {
469 checks.push(Kit::check_slices);
470 }
471 for check in checks {
472 if let Err(mut r) = check(self, resolver) {
473 failures.append(&mut r.failures);
474 }
475 }
476 report(failures)
477 }
478
479 pub fn check_slices(&self, resolver: &dyn Resolver) -> Result<(), Report> {
483 self.setup_ok()?;
484 let mut f = Failures::new("slices");
485 let keys = self.fixtures.slices.keys();
486 let members = self.manifest_members();
487 for case in &self.slice_cases {
488 let Ok(r) = Receipt::from_json(&case.bytes) else {
489 f.push(case.name, "the fixture does not parse".to_string());
490 continue;
491 };
492 let rec = Recording::new(resolver);
493 let out = verify(&case.bytes, &keys, Some(&rec), None);
494 let calls = rec.take();
495 let want_code = match case.name {
496 "slice-manifest-missing" | "slice-member-missing" => Some(ErrorCode::InputUnavailable),
497 _ => None,
498 };
499 match want_code {
500 Some(code) => {
501 if out.ok || out.code != Some(code) {
502 f.push(case.name, format!("want {code}, got ok={} code={:?} {}", out.ok, out.code, out.path));
503 }
504 }
505 None => {
506 let passed = out.ok
507 && ((out.level == Some(Level::Recomputed) && out.code.is_none())
508 || (out.level == Some(Level::Signed) && out.code == Some(ErrorCode::EvalUnavailable)));
509 if !passed {
510 f.push(
511 case.name,
512 format!(
513 "want step 7.5 to pass (recomputed, or E_EVAL_UNAVAILABLE), got ok={} level={:?} code={:?} {} {}",
514 out.ok, out.level, out.code, out.path, out.detail
515 ),
516 );
517 }
518 }
519 }
520 let want = expected_calls_with(&r, &out, &members);
521 let same = match out.code {
522 Some(ErrorCode::ValueMismatch | ErrorCode::InputSuperseded) => {
524 calls.starts_with(&want) && calls[want.len()..].iter().all(|c| matches!(c, Call::LookupReceipt(_)))
525 }
526 _ => calls == want,
527 };
528 if !same {
529 f.push(case.name, format!("asked {calls:?}, step 7 needs {want:?} (result {:?})", out.code));
530 }
531 if want_code.is_none() {
533 if let Some(Evaluation { lineage: Some(lineage), .. }) = resolver.evaluation(r.cell(), r.asof()) {
534 for e in &lineage {
535 let form = crate::lexical::check_input_entry(e, "").is_ok();
536 let backed = r.inputs().contains(e)
537 || if e.starts_with("slice:") {
538 resolver.manifest(e).is_some()
539 } else {
540 resolver.receipt(e).is_some()
541 };
542 if !form || !backed {
543 f.push(case.name, format!("lineage entry {} is not an entry the resolver backs", shown(e)));
544 }
545 }
546 }
547 }
548 }
549 let sf = &self.fixtures.slices;
551 for (e, bytes) in &sf.manifests {
552 for _ in 0..2 {
553 match resolver.manifest(e) {
554 Some(b) if *b == **bytes => {}
555 Some(b) => f.push(
556 "bytes",
557 format!("manifest {e}: {} bytes differ from the {} loaded", b.len(), bytes.len()),
558 ),
559 None => f.push("bytes", format!("manifest {e}: None, but it was loaded")),
560 }
561 }
562 }
563 for (h, bytes) in &sf.receipts {
564 match resolver.receipt(h) {
565 Some(b) if *b == **bytes => {}
566 Some(_) => f.push("bytes", format!("receipt {h}: the bytes differ from those loaded")),
567 None => f.push("bytes", format!("receipt {h}: None, but it was loaded")),
568 }
569 }
570 for (h, bytes) in &sf.descriptors {
571 match resolver.descriptor(h) {
572 Some(b) if *b == **bytes => {}
573 Some(_) => f.push("bytes", format!("descriptor {h}: the bytes differ from those loaded")),
574 None => f.push("bytes", format!("descriptor {h}: None, but it was loaded")),
575 }
576 }
577 let known = sf.manifests.first().map(|(e, _)| e.clone()).unwrap_or_default();
579 let missing: Vec<String> = self
580 .slice_cases
581 .iter()
582 .filter_map(|c| Receipt::from_json(&c.bytes).ok())
583 .flat_map(|r| r.inputs().to_vec())
584 .filter(|e| e.starts_with("slice:") && !sf.manifests.iter().any(|(x, _)| x == e))
585 .collect();
586 let mut probes = vec![
587 format!("slice:{}:{}", "0".repeat(64), "0".repeat(64)),
588 String::new(),
589 "slice:".into(),
590 known.to_uppercase(),
591 format!("{known}0"),
592 format!(" {known}"),
593 known.get(..134).unwrap_or_default().to_string(),
594 known.replacen("slice:", "SLICE:", 1),
595 "' OR '1'='1".into(),
596 "../../etc/passwd".into(),
597 "a".repeat(100_000),
598 ];
599 probes.extend(sf.receipts.first().map(|(h, _)| h.clone()));
600 probes.extend(missing);
601 for p in &probes {
602 if resolver.manifest(p).is_some() {
603 f.push("lookup", format!("manifest({}) is not None", shown(p)));
604 }
605 }
606 f.finish()
607 }
608
609 fn manifest_members(&self) -> Vec<(String, Vec<String>)> {
611 self.fixtures
612 .slices
613 .manifests
614 .iter()
615 .filter_map(|(e, b)| Manifest::from_json(b).ok().map(|m| (e.clone(), m.members().to_vec())))
616 .collect()
617 }
618
619 pub fn check_lazy_calls(&self, resolver: &dyn Resolver) -> Result<(), Report> {
622 self.setup_ok()?;
623 let keys = self.fixtures.keys();
624 let mut f = Failures::new("lazy_calls");
625 for case in &self.cases {
626 let rec = Recording::new(resolver);
627 let without = verify(&case.bytes, &keys, None, None);
628 let out = verify(&case.bytes, &keys, Some(&rec), None);
629 let calls = rec.take();
630 if !without.ok {
631 if !calls.is_empty() {
632 f.push(
633 case.name,
634 format!("steps 1-6 fail ({:?}), yet the resolver was asked {calls:?}", without.code),
635 );
636 }
637 if out != without {
638 f.push(case.name, format!("a resolver changed a failure before step 7: {out:?}"));
639 }
640 continue;
641 }
642 match Receipt::from_json(&case.bytes) {
643 Ok(r) => {
644 let want = expected_calls(&r, &out);
645 let same = match out.code {
646 Some(ErrorCode::ValueMismatch | ErrorCode::InputSuperseded) => {
648 calls.starts_with(&want)
649 && calls[want.len()..].iter().all(|c| matches!(c, Call::LookupReceipt(_)))
650 }
651 _ => calls == want,
652 };
653 if !same {
654 f.push(case.name, format!("asked {calls:?}, step 7 needs {want:?} (result {:?})", out.code));
655 }
656 }
657 Err(e) => f.push(case.name, format!("steps 1-6 passed but the receipt does not parse: {e}")),
658 }
659 }
660 for name in ["tampered-value", "duplicate-member", NOT_JSON] {
661 if let Some(case) = self.case(name) {
662 if verify(&case.bytes, &keys, None, None).ok {
663 f.push(name, "expected to fail before step 7".to_string());
664 }
665 }
666 }
667 f.finish()
668 }
669
670 pub fn check_unavailable(&self, resolver: &dyn Resolver) -> Result<(), Report> {
672 self.setup_ok()?;
673 let mut f = Failures::new("unavailable");
674 let keys = self.fixtures.keys();
675 self.expect(&mut f, resolver, &keys, "instant-no-valid-time", Want::Fails(ErrorCode::FnUnavailable));
677 self.expect(&mut f, resolver, &keys, "computed-two-inputs", Want::Fails(ErrorCode::InputUnavailable));
678 self.expect(&mut f, resolver, &keys, "first-input-missing", Want::Fails(ErrorCode::InputUnavailable));
679 self.expect(&mut f, resolver, &keys, "computed-with-lineage", Want::EvalUnavailable);
680 let unknown_fn = self.receipt_of("instant-no-valid-time").map(|r| r.cell().fn_hash.clone());
682 let known_receipt = self.fixtures.receipts.first().map(|(h, _)| h.clone()).unwrap_or_default();
683 let known_fn = self.fixtures.descriptors.first().map(|(h, _)| h.clone()).unwrap_or_default();
684 let mut probes: Vec<String> = vec![
685 "0".repeat(64),
686 "f".repeat(64),
687 String::new(),
688 "abc".into(),
689 "g".repeat(64),
690 "\0".into(),
691 format!("{known_receipt}\0"),
692 "../../etc/passwd".into(),
693 "' OR '1'='1".into(),
694 "%".into(),
695 "_".repeat(64),
696 "é".repeat(32),
697 "a".repeat(100_000),
698 known_receipt.to_uppercase(),
699 known_fn.to_uppercase(),
700 format!("{known_receipt}0"),
701 format!(" {known_receipt}"),
702 format!("{known_fn}\n"),
703 known_receipt.get(..63).unwrap_or_default().to_string(),
704 ];
705 probes.extend(unknown_fn);
706 for name in ["computed-two-inputs", "first-input-missing"] {
709 if let Some(r) = self.receipt_of(name) {
710 probes.extend(r.inputs().iter().filter(|h| !self.has_receipt(h)).cloned());
711 }
712 }
713 for p in &probes {
714 if resolver.descriptor(p).is_some() {
715 f.push("lookup", format!("descriptor({}) is not None", shown(p)));
716 }
717 if resolver.receipt(p).is_some() {
718 f.push("lookup", format!("receipt({}) is not None", shown(p)));
719 }
720 }
721 if let Some(r) = self.receipt_of("timeline-1") {
723 for asof in ["", "not-an-instant", "\0", "2024-03-16", "' OR '1'='1", "2024-03-16T09:00:00Z"] {
724 let _ = resolver.evaluate(r.cell(), asof);
725 }
726 }
727 f.finish()
728 }
729
730 pub fn check_bytes(&self, resolver: &dyn Resolver) -> Result<(), Report> {
732 self.setup_ok()?;
733 let mut f = Failures::new("bytes");
734 for (h, bytes) in &self.fixtures.descriptors {
735 for _ in 0..2 {
736 match resolver.descriptor(h) {
737 Some(b) if *b == **bytes => {}
738 Some(b) => {
739 f.push("descriptor", format!("{h}: {} bytes differ from the {} loaded", b.len(), bytes.len()))
740 }
741 None => f.push("descriptor", format!("{h}: None, but it was loaded")),
742 }
743 }
744 }
745 for (h, bytes) in &self.fixtures.receipts {
746 for _ in 0..2 {
747 match resolver.receipt(h) {
748 Some(b) if *b == **bytes => {}
749 Some(b) => {
750 f.push("receipt", format!("{h}: {} bytes differ from the {} loaded", b.len(), bytes.len()))
751 }
752 None => f.push("receipt", format!("{h}: None, but it was loaded")),
753 }
754 }
755 }
756 f.finish()
757 }
758
759 pub fn check_asof(&self, resolver: &dyn Resolver) -> Result<(), Report> {
762 self.setup_ok()?;
763 let mut f = Failures::new("asof");
764 let keys = self.fixtures.keys();
765 for case in &self.cases {
767 let Ok(r) = Receipt::from_json(&case.bytes) else { continue };
768 let rec = Recording::new(resolver);
769 let _ = verify(&case.bytes, &keys, Some(&rec), None);
770 for call in rec.take() {
771 if let Call::Evaluate { cell_id, asof } = call {
772 if cell_id != r.cell().id() || asof != r.asof() {
773 f.push(case.name, format!("evaluate got ({cell_id}, {asof}), not the receipt's cell and asof"));
774 }
775 }
776 }
777 }
778 self.expect(&mut f, resolver, &keys, "stored-number", Want::Recomputed);
779 if !self.time_aware {
780 return f.finish();
781 }
782 for name in ["timeline-1", "timeline-1-later", "timeline-2"] {
785 self.expect(&mut f, resolver, &keys, name, Want::Recomputed);
786 }
787 self.expect(&mut f, resolver, &keys, "timeline-0", Want::EvalUnavailable);
788 let (Some(v1), Some(v2)) = (self.receipt_of("timeline-1"), self.receipt_of("timeline-2")) else {
792 f.push("timeline", "fixtures missing".to_string());
793 return f.finish();
794 };
795 let cell = v1.cell();
796 let points: Vec<(&str, Option<&Scalar>)> = match self.answer_rule {
797 AnswerRule::TxOnly => vec![
798 ("0001-01-01T00:00:00.000000Z", None),
799 ("2024-03-15T15:59:59.999999Z", None),
800 (v1.tx(), Some(v1.value())),
801 ("2024-03-17T00:00:00.000000Z", Some(v1.value())),
802 ("2024-03-18T09:59:59.999999Z", Some(v1.value())),
803 (v2.tx(), Some(v2.value())),
804 ("9999-12-31T23:59:59.999999Z", Some(v2.value())),
805 ],
806 AnswerRule::TxAndAsof => vec![
807 ("0001-01-01T00:00:00.000000Z", None),
808 ("2024-03-15T15:59:59.999999Z", None),
809 (v1.tx(), None),
810 ("2024-03-16T08:59:59.999999Z", None),
811 (v1.asof(), Some(v1.value())),
812 ("2024-03-17T00:00:00.000000Z", Some(v1.value())),
813 ("2024-03-18T09:59:59.999999Z", Some(v1.value())),
814 (v2.tx(), Some(v2.value())),
815 ("9999-12-31T23:59:59.999999Z", Some(v2.value())),
816 ],
817 };
818 for (asof, want) in points {
819 let got = resolver.evaluate(cell, asof);
820 if got.as_ref() != want {
821 f.push("timeline", format!("evaluate as of {asof} gave {got:?}, want {want:?}"));
822 }
823 }
824 f.finish()
825 }
826
827 fn setup_ok(&self) -> Result<(), Report> {
828 if self.setup.is_empty() {
829 Ok(())
830 } else {
831 Err(Report { failures: self.setup.clone() })
832 }
833 }
834
835 fn case(&self, name: &str) -> Option<&Case> {
836 self.cases.iter().find(|c| c.name == name)
837 }
838
839 fn receipt_of(&self, name: &str) -> Option<Receipt> {
840 self.case(name).and_then(|c| Receipt::from_json(&c.bytes).ok())
841 }
842
843 fn has_receipt(&self, hash: &str) -> bool {
844 self.fixtures.receipts.iter().any(|(h, _)| h == hash)
845 }
846
847 fn expect(&self, f: &mut Failures, resolver: &dyn Resolver, keys: &Keys, name: &str, want: Want) {
849 let Some(case) = self.case(name) else {
850 f.push(name, "no such fixture".to_string());
851 return;
852 };
853 let out = verify(&case.bytes, keys, Some(resolver), None);
854 let good = match want {
855 Want::Recomputed => out.ok && out.level == Some(Level::Recomputed) && out.code.is_none(),
856 Want::EvalUnavailable => {
857 out.ok && out.level == Some(Level::Signed) && out.code == Some(ErrorCode::EvalUnavailable)
858 }
859 Want::Fails(code) => !out.ok && out.code == Some(code),
860 };
861 if !good {
862 f.push(
863 name,
864 format!(
865 "want {want:?}, got ok={} level={:?} code={:?} {} {}",
866 out.ok, out.level, out.code, out.path, out.detail
867 ),
868 );
869 }
870 }
871}
872
873fn load_slices(sf: &mut SliceFixtures, setup: &mut Vec<String>) -> Vec<Case> {
876 let mut cases = Vec::new();
877 for (host, text) in data::SLICE_KEY_DOCUMENTS {
878 sf.key_documents.push((host.to_string(), text.as_bytes().to_vec()));
879 }
880 for (source, text) in data::SLICE_DESCRIPTORS {
881 match crate::descriptor::FunctionDescriptor::from_json(text.as_bytes()) {
882 Ok(d) => sf.descriptors.push((d.hash(), text.as_bytes().to_vec())),
883 Err(e) => setup.push(format!("setup: {source}: {e}")),
884 }
885 }
886 for (name, source, text) in data::SLICE_RECEIPTS {
887 match Receipt::from_json(text.as_bytes()) {
888 Ok(r) => sf.receipts.push((r.hash().to_string(), text.as_bytes().to_vec())),
889 Err(e) => setup.push(format!("setup: {source}: {e}")),
890 }
891 if *name != "member" {
892 cases.push(Case { name, bytes: text.as_bytes().to_vec() });
893 }
894 }
895 for (source, text) in data::MANIFESTS {
896 match Manifest::from_json(text.as_bytes()) {
897 Ok(m) => sf.manifests.push((m.entry(), text.as_bytes().to_vec())),
898 Err(e) => setup.push(format!("setup: {source}: {e}")),
899 }
900 }
901 for (name, _, text) in data::SLICE_PROBES {
902 cases.push(Case { name, bytes: text.as_bytes().to_vec() });
903 }
904 cases
905}
906
907type Check = fn(&Kit, &dyn Resolver) -> Result<(), Report>;
909
910#[derive(Clone, Copy, Debug)]
912enum Want {
913 Recomputed,
914 EvalUnavailable,
915 Fails(ErrorCode),
916}
917
918fn expected_calls(r: &Receipt, out: &VerifyResult) -> Vec<Call> {
920 expected_calls_with(r, out, &[])
921}
922
923fn entry_calls(e: &str, members: &[(String, Vec<String>)], take: Option<usize>) -> Vec<Call> {
926 if !e.starts_with("slice:") {
927 return vec![Call::LookupReceipt(e.to_string())];
928 }
929 let mut calls = vec![Call::LookupManifest(e.to_string())];
930 if let Some((_, ms)) = members.iter().find(|(x, _)| x == e) {
931 calls.extend(ms.iter().take(take.unwrap_or(ms.len())).map(|h| Call::LookupReceipt(h.clone())));
932 }
933 calls
934}
935
936fn expected_calls_with(r: &Receipt, out: &VerifyResult, members: &[(String, Vec<String>)]) -> Vec<Call> {
938 let mut calls = vec![Call::LookupDescriptor(r.cell().fn_hash.clone())];
939 let all_inputs = || r.inputs().iter().flat_map(|e| entry_calls(e, members, None));
940 let evaluate = Call::Evaluate { cell_id: r.cell().id(), asof: r.asof().to_string() };
941 match out.code {
942 None | Some(ErrorCode::ValueMismatch | ErrorCode::EvalUnavailable | ErrorCode::InputSuperseded) => {
944 calls.extend(all_inputs());
945 calls.push(evaluate);
946 }
947 Some(_) if out.path.starts_with("$.inputs.") => {
950 let rest = &out.path["$.inputs.".len()..];
951 let at = r.inputs().iter().position(|e| {
952 rest == e.as_str() || rest.starts_with(&format!("{e}.")) || rest.starts_with(&format!("{e}["))
953 });
954 let Some(i) = at else {
955 calls.extend(all_inputs());
956 return calls;
957 };
958 calls.extend(r.inputs()[..i].iter().flat_map(|e| entry_calls(e, members, None)));
959 let e = &r.inputs()[i];
960 let member = rest[e.len()..]
961 .strip_prefix(".members.")
962 .and_then(|h| members.iter().find(|(x, _)| x == e).and_then(|(_, ms)| ms.iter().position(|m| m == h)));
963 calls.extend(entry_calls(e, members, Some(member.map_or(0, |k| k + 1))));
964 }
965 Some(ErrorCode::TimeOrder) if out.path == "$.tx" => calls.extend(all_inputs()),
967 Some(_) => {}
969 }
970 calls
971}
972
973fn shown(s: &str) -> String {
975 if s.chars().count() > 80 {
976 format!("{:?}… ({} bytes)", s.chars().take(16).collect::<String>(), s.len())
977 } else {
978 format!("{s:?}")
979 }
980}
981
982struct Failures {
983 check: &'static str,
984 lines: Vec<String>,
985}
986
987impl Failures {
988 fn new(check: &'static str) -> Failures {
989 Failures { check, lines: Vec::new() }
990 }
991
992 fn push(&mut self, case: &str, what: String) {
993 self.lines.push(format!("{}: {case}: {what}", self.check));
994 }
995
996 fn finish(self) -> Result<(), Report> {
997 report(self.lines)
998 }
999}
1000
1001fn report(failures: Vec<String>) -> Result<(), Report> {
1002 if failures.is_empty() {
1003 Ok(())
1004 } else {
1005 Err(Report { failures })
1006 }
1007}