1use crate::{
2 ast::{BinOp, DataTypeKey, IfBranch, OnTestFailure, Span, TypedArg, TypedDataType, TypedTest},
3 expr::{TypedExpr, UntypedExpr},
4 format::Formatter,
5 gen_uplc::CodeGenerator,
6 plutus_version::PlutusVersion,
7 tipo::{Type, convert_opaque_type},
8};
9use cryptoxide::{blake2b::Blake2b, digest::Digest};
10use indexmap::IndexMap;
11use itertools::Itertools;
12use owo_colors::{OwoColorize, Stream, Stream::Stderr};
13use pallas_primitives::alonzo::{Constr, PlutusData};
14use patricia_tree::PatriciaMap;
15#[cfg(not(target_family = "wasm"))]
16use std::time::Duration;
17use std::{
18 borrow::Borrow,
19 collections::BTreeMap,
20 convert::TryFrom,
21 fmt::{Debug, Display},
22 ops::Deref,
23 path::PathBuf,
24 rc::Rc,
25};
26use uplc::{
27 ast::{Constant, Data, Name, NamedDeBruijn, Program, Term},
28 machine::{cost_model::ExBudget, eval_result::EvalResult},
29};
30use vec1::{Vec1, vec1};
31
32#[derive(Debug, Clone, Copy)]
33pub enum RunnableKind {
34 Test,
35 Bench,
36}
37
38#[derive(Debug, Clone)]
56pub enum Test {
57 UnitTest(UnitTest),
58 PropertyTest(PropertyTest),
59 Benchmark(Benchmark),
60}
61
62unsafe impl Send for Test {}
63
64impl Test {
65 pub fn unit_test(
66 generator: &mut CodeGenerator<'_>,
67 test: TypedTest,
68 module_name: String,
69 input_path: PathBuf,
70 ) -> Test {
71 let program = generator.generate_raw(&test.body, &[], &module_name);
72
73 let assertion = match test.body.try_into() {
74 Err(..) => None,
75 Ok(Assertion { bin_op, head, tail }) => {
76 let as_constant = |generator: &mut CodeGenerator<'_>, side| {
77 Program::<NamedDeBruijn>::try_from(generator.generate_raw(
78 &side,
79 &[],
80 &module_name,
81 ))
82 .expect("failed to convert assertion operaand to NamedDeBruijn")
83 .eval(ExBudget::max())
84 .unwrap_constant()
85 .map(|cst| (cst, side.tipo()))
86 };
87
88 Some(Assertion {
90 bin_op,
91 head: as_constant(generator, head.expect("cannot be Err at this point")),
92 tail: tail
93 .expect("cannot be Err at this point")
94 .try_mapped(|e| as_constant(generator, e)),
95 })
96 }
97 };
98
99 Test::UnitTest(UnitTest {
100 input_path,
101 module: module_name,
102 name: test.name,
103 program,
104 assertion,
105 on_test_failure: test.on_test_failure,
106 })
107 }
108
109 pub fn property_test(
110 input_path: PathBuf,
111 module: String,
112 name: String,
113 on_test_failure: OnTestFailure,
114 program: Program<Name>,
115 fuzzer: Fuzzer<Name>,
116 ) -> Test {
117 Test::PropertyTest(PropertyTest {
118 input_path,
119 module,
120 name,
121 program,
122 on_test_failure,
123 fuzzer,
124 })
125 }
126
127 pub fn from_function_definition(
128 generator: &mut CodeGenerator<'_>,
129 test: TypedTest,
130 module_name: String,
131 input_path: PathBuf,
132 kind: RunnableKind,
133 ) -> Test {
134 if test.arguments.is_empty() {
135 if matches!(kind, RunnableKind::Bench) {
136 unreachable!("benchmark must have at least one argument");
137 } else {
138 Self::unit_test(generator, test, module_name, input_path)
139 }
140 } else {
141 let parameter = test.arguments.first().unwrap().to_owned();
142
143 let via = parameter.via.clone();
144
145 let type_info = parameter.arg.tipo.clone();
146
147 let stripped_type_info = convert_opaque_type(&type_info, generator.data_types(), true);
148
149 let program = generator.clone().generate_raw(
150 &test.body,
151 &[TypedArg {
152 tipo: stripped_type_info.clone(),
153 ..parameter.clone().into()
154 }],
155 &module_name,
156 );
157
158 let generator_program = generator.clone().generate_raw(&via, &[], &module_name);
162
163 match kind {
164 RunnableKind::Bench => Test::Benchmark(Benchmark {
165 input_path,
166 module: module_name,
167 name: test.name,
168 program,
169 on_test_failure: test.on_test_failure,
170 sampler: Sampler {
171 program: generator_program,
172 type_info,
173 stripped_type_info,
174 },
175 }),
176 RunnableKind::Test => Self::property_test(
177 input_path,
178 module_name,
179 test.name,
180 test.on_test_failure,
181 program,
182 Fuzzer {
183 program: generator_program,
184 stripped_type_info,
185 type_info,
186 },
187 ),
188 }
189 }
190 }
191
192 pub fn run(
193 self,
194 seed: u32,
195 max_success: usize,
196 plutus_version: &PlutusVersion,
197 ) -> TestResult<(Constant, Rc<Type>), PlutusData> {
198 match self {
199 Test::UnitTest(unit_test) => TestResult::UnitTestResult(unit_test.run(plutus_version)),
200 Test::PropertyTest(property_test) => {
201 TestResult::PropertyTestResult(property_test.run(seed, max_success, plutus_version))
202 }
203 Test::Benchmark(benchmark) => {
204 TestResult::BenchmarkResult(benchmark.run(seed, max_success, plutus_version))
205 }
206 }
207 }
208}
209
210#[derive(Debug, Clone)]
213pub struct UnitTest {
214 pub input_path: PathBuf,
215 pub module: String,
216 pub name: String,
217 pub on_test_failure: OnTestFailure,
218 pub program: Program<Name>,
219 pub assertion: Option<Assertion<(Constant, Rc<Type>)>>,
220}
221
222unsafe impl Send for UnitTest {}
223
224impl UnitTest {
225 pub fn run(self, plutus_version: &PlutusVersion) -> UnitTestResult<(Constant, Rc<Type>)> {
226 let eval_result = Program::<NamedDeBruijn>::try_from(self.program.clone())
227 .unwrap()
228 .eval_version(ExBudget::max(), &plutus_version.into());
229
230 let success = !eval_result.failed(match self.on_test_failure {
231 OnTestFailure::SucceedEventually | OnTestFailure::SucceedImmediately => true,
232 OnTestFailure::FailImmediately => false,
233 });
234
235 let mut logs = Vec::new();
236 if let Err(err) = eval_result.result() {
237 logs.push(format!("{err}"))
238 }
239 logs.extend(eval_result.logs());
240
241 UnitTestResult {
242 success,
243 test: self.to_owned(),
244 spent_budget: eval_result.cost(),
245 logs,
246 assertion: self.assertion,
247 }
248 }
249}
250
251#[derive(Debug, Clone)]
254pub struct PropertyTest {
255 pub input_path: PathBuf,
256 pub module: String,
257 pub name: String,
258 pub on_test_failure: OnTestFailure,
259 pub program: Program<Name>,
260 pub fuzzer: Fuzzer<Name>,
261}
262
263unsafe impl Send for PropertyTest {}
264
265#[derive(Debug, Clone)]
266pub struct Fuzzer<T> {
267 pub program: Program<T>,
268
269 pub type_info: Rc<Type>,
270
271 pub stripped_type_info: Rc<Type>,
275}
276
277#[derive(Debug, Clone, thiserror::Error, miette::Diagnostic)]
278#[error("Fuzzer exited unexpectedly: {uplc_error}.")]
279pub struct FuzzerError {
280 logs: Vec<String>,
281 uplc_error: uplc::machine::Error,
282}
283
284#[derive(Debug, Clone)]
285pub enum Event {
286 Simplifying {
287 choices: usize,
288 },
289 Simplified {
290 #[cfg(not(target_family = "wasm"))]
291 duration: Duration,
292 #[cfg(target_family = "wasm")]
293 duration: (),
294 steps: usize,
295 },
296}
297
298impl Display for Event {
299 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
300 match self {
301 Event::Simplifying { choices } => f.write_str(&format!(
302 "{} {}",
303 " Simplifying"
304 .if_supports_color(Stderr, |s| s.bold())
305 .if_supports_color(Stderr, |s| s.purple()),
306 format!("counterexample from {choices} choices")
307 .if_supports_color(Stderr, |s| s.bold()),
308 )),
309 #[cfg(target_family = "wasm")]
310 Event::Simplified { steps, .. } => f.write_str(&format!(
311 "{} {}",
312 " Simplified"
313 .if_supports_color(Stderr, |s| s.bold())
314 .if_supports_color(Stderr, |s| s.purple()),
315 format!("counterexample after {steps} steps",)
316 .if_supports_color(Stderr, |s| s.bold()),
317 )),
318 #[cfg(not(target_family = "wasm"))]
319 Event::Simplified { duration, steps } => f.write_str(&format!(
320 "{} {}",
321 " Simplified"
322 .if_supports_color(Stderr, |s| s.bold())
323 .if_supports_color(Stderr, |s| s.purple()),
324 format!(
325 "counterexample in {} after {steps} steps",
326 if duration.as_secs() == 0 {
327 format!("{}ms", duration.as_millis())
328 } else {
329 format!("{}s", duration.as_secs())
330 }
331 )
332 .if_supports_color(Stderr, |s| s.bold()),
333 )),
334 }
335 }
336}
337
338impl PropertyTest {
339 pub const DEFAULT_MAX_SUCCESS: usize = 100;
340
341 pub fn run(
344 self,
345 seed: u32,
346 n: usize,
347 plutus_version: &PlutusVersion,
348 ) -> PropertyTestResult<PlutusData> {
349 let mut labels = BTreeMap::new();
350 let mut remaining = n;
351
352 let (logs, counterexample, iterations) = match self.run_n_times(
353 &mut remaining,
354 Prng::from_seed(seed),
355 &mut labels,
356 plutus_version,
357 ) {
358 Ok(None) => (Vec::new(), Ok(None), n),
359 Ok(Some(counterexample)) => (
360 self.eval(&counterexample.value, plutus_version).logs(),
361 Ok(Some(counterexample.value)),
362 n - remaining,
363 ),
364 Err(FuzzerError { logs, uplc_error }) => (logs, Err(uplc_error), n - remaining + 1),
365 };
366
367 PropertyTestResult {
368 test: self,
369 counterexample,
370 iterations,
371 labels,
372 logs,
373 }
374 }
375
376 pub fn run_n_times<'a>(
377 &'a self,
378 remaining: &mut usize,
379 initial_prng: Prng,
380 labels: &mut BTreeMap<String, usize>,
381 plutus_version: &'a PlutusVersion,
382 ) -> Result<Option<Counterexample<'a>>, FuzzerError> {
383 let mut prng = initial_prng;
384 let mut counterexample = None;
385
386 while *remaining > 0 && counterexample.is_none() {
387 (prng, counterexample) = self.run_once(prng, labels, plutus_version)?;
388 *remaining -= 1;
389 }
390
391 Ok(counterexample)
392 }
393
394 fn run_once<'a>(
395 &'a self,
396 prng: Prng,
397 labels: &mut BTreeMap<String, usize>,
398 plutus_version: &'a PlutusVersion,
399 ) -> Result<(Prng, Option<Counterexample<'a>>), FuzzerError> {
400 use OnTestFailure::*;
401
402 let (next_prng, value) = prng
403 .sample(&self.fuzzer.program)?
404 .expect("A seeded PRNG returned 'None' which indicates a fuzzer is ill-formed and implemented wrongly; please contact library's authors.");
405
406 let result = self.eval(&value, plutus_version);
407
408 for label in result.labels() {
409 labels
413 .entry(label)
414 .and_modify(|count| *count += 1)
415 .or_insert(1);
416 }
417
418 let is_failure = result.failed(false);
419
420 let is_success = !is_failure;
421
422 let keep_counterexample = match self.on_test_failure {
423 FailImmediately | SucceedImmediately => is_failure,
424 SucceedEventually => is_success,
425 };
426
427 if keep_counterexample {
428 let mut counterexample = Counterexample {
429 value,
430 choices: next_prng.choices(),
431 cache: Cache::new(|choices| {
432 match Prng::from_choices(choices).sample(&self.fuzzer.program) {
433 Err(..) => Status::Invalid,
434 Ok(None) => Status::Invalid,
435 Ok(Some((_, value))) => {
436 let result = self.eval(&value, plutus_version);
437
438 let is_failure = result.failed(false);
439
440 match self.on_test_failure {
441 FailImmediately | SucceedImmediately => {
442 if is_failure {
443 Status::Keep(value)
444 } else {
445 Status::Ignore
446 }
447 }
448
449 SucceedEventually => {
450 if is_failure {
451 Status::Ignore
452 } else {
453 Status::Keep(value)
454 }
455 }
456 }
457 }
458 }
459 }),
460 };
461
462 if !counterexample.choices.is_empty() {
463 counterexample.simplify();
464 }
465
466 Ok((next_prng, Some(counterexample)))
467 } else {
468 Ok((next_prng, None))
469 }
470 }
471
472 pub fn eval(&self, value: &PlutusData, plutus_version: &PlutusVersion) -> EvalResult {
473 let program = self.program.apply_data(value.clone());
474
475 Program::<NamedDeBruijn>::try_from(program)
476 .unwrap()
477 .eval_version(ExBudget::max(), &plutus_version.into())
478 }
479}
480
481#[derive(Debug, Clone)]
484pub struct Sampler<T> {
485 pub program: Program<T>,
486
487 pub type_info: Rc<Type>,
488
489 pub stripped_type_info: Rc<Type>,
493}
494
495#[derive(Debug, Clone, thiserror::Error, miette::Diagnostic)]
496pub enum BenchmarkError {
497 #[error("Sampler exited unexpectedly: {uplc_error}.")]
498 SamplerError {
499 logs: Vec<String>,
500 uplc_error: uplc::machine::Error,
501 },
502 #[error("Bench exited unexpectedly: {uplc_error}.")]
503 BenchError {
504 logs: Vec<String>,
505 uplc_error: uplc::machine::Error,
506 },
507}
508
509impl BenchmarkError {
510 pub fn logs(&self) -> &[String] {
511 match self {
512 BenchmarkError::SamplerError { logs, .. } | BenchmarkError::BenchError { logs, .. } => {
513 logs.as_slice()
514 }
515 }
516 }
517}
518
519#[derive(Debug, Clone)]
520pub struct Benchmark {
521 pub input_path: PathBuf,
522 pub module: String,
523 pub name: String,
524 pub on_test_failure: OnTestFailure,
525 pub program: Program<Name>,
526 pub sampler: Sampler<Name>,
527}
528
529unsafe impl Send for Benchmark {}
530
531impl Benchmark {
532 pub const DEFAULT_MAX_SIZE: usize = 30;
533
534 pub fn run(
535 self,
536 seed: u32,
537 max_size: usize,
538 plutus_version: &PlutusVersion,
539 ) -> BenchmarkResult {
540 let mut measures = Vec::with_capacity(max_size);
541 let mut prng = Prng::from_seed(seed);
542 let mut error = None;
543 let mut size = 0;
544
545 while error.is_none() && max_size >= size {
546 let fuzzer = self
547 .sampler
548 .program
549 .apply_term(&Term::Constant(Constant::Integer(size.into()).into()));
550
551 match prng.sample(&fuzzer) {
552 Ok(None) => {
553 panic!(
554 "A seeded PRNG returned 'None' which indicates a sampler is ill-formed and implemented wrongly; please contact library's authors."
555 );
556 }
557
558 Ok(Some((new_prng, value))) => {
559 prng = new_prng;
560 let result = self.eval(&value, plutus_version);
561 match result.result() {
562 Ok(_) => measures.push((size, result.cost())),
563 Err(uplc_error) => {
564 error = Some(BenchmarkError::BenchError {
565 logs: result.logs(),
566 uplc_error,
567 });
568 }
569 }
570 }
571
572 Err(FuzzerError { logs, uplc_error }) => {
573 error = Some(BenchmarkError::SamplerError { logs, uplc_error });
574 }
575 }
576
577 size += 1;
578 }
579
580 BenchmarkResult {
581 bench: self,
582 measures,
583 error,
584 }
585 }
586
587 pub fn eval(&self, value: &PlutusData, plutus_version: &PlutusVersion) -> EvalResult {
588 let program = self.program.apply_data(value.clone());
589
590 Program::<NamedDeBruijn>::try_from(program)
591 .unwrap()
592 .eval_version(ExBudget::max(), &plutus_version.into())
593 }
594}
595
596#[derive(Debug)]
615pub enum Prng {
616 Seeded { choices: Vec<u8>, uplc: PlutusData },
617 Replayed { choices: Vec<u8>, uplc: PlutusData },
618}
619
620impl Prng {
621 const SEEDED: u64 = 0;
623 const REPLAYED: u64 = 1;
625
626 const SOME: u64 = 0;
628 const NONE: u64 = 1;
630
631 pub fn uplc(&self) -> PlutusData {
632 match self {
633 Prng::Seeded { uplc, .. } => uplc.clone(),
634 Prng::Replayed { uplc, .. } => uplc.clone(),
635 }
636 }
637
638 pub fn choices(&self) -> Vec<u8> {
639 match self {
640 Prng::Seeded { choices, .. } => {
641 let mut choices = choices.to_vec();
642 choices.reverse();
643 choices
644 }
645 Prng::Replayed { choices, .. } => choices.to_vec(),
646 }
647 }
648
649 pub fn from_seed(seed: u32) -> Prng {
651 let mut digest = [0u8; 32];
652 let mut context = Blake2b::new(32);
653 context.input(&seed.to_be_bytes()[..]);
654 context.result(&mut digest);
655
656 Prng::Seeded {
657 choices: vec![],
658 uplc: Data::constr(
659 Prng::SEEDED,
660 vec![
661 Data::bytestring(digest.to_vec()), Data::bytestring(vec![]), ],
664 ),
665 }
666 }
667
668 pub fn from_choices(choices: &[u8]) -> Prng {
670 Prng::Replayed {
671 uplc: Data::constr(
672 Prng::REPLAYED,
673 vec![
674 Data::integer(choices.len().into()),
675 Data::bytestring(choices.iter().rev().cloned().collect::<Vec<_>>()),
676 ],
677 ),
678 choices: choices.to_vec(),
679 }
680 }
681
682 pub fn sample(
684 &self,
685 fuzzer: &Program<Name>,
686 ) -> Result<Option<(Prng, PlutusData)>, FuzzerError> {
687 let program = Program::<NamedDeBruijn>::try_from(fuzzer.apply_data(self.uplc())).unwrap();
688 let result = program.eval(ExBudget::max());
689 result
690 .result()
691 .map_err(|uplc_error| FuzzerError {
692 logs: result.logs(),
693 uplc_error,
694 })
695 .map(Prng::from_result)
696 }
697
698 pub fn from_result(result: Term<NamedDeBruijn>) -> Option<(Self, PlutusData)> {
709 fn as_prng(cst: &PlutusData) -> Prng {
711 if let PlutusData::Constr(Constr { tag, fields, .. }) = cst {
712 if *tag == 121 + Prng::SEEDED {
713 if let [
714 PlutusData::BoundedBytes(bytes),
715 PlutusData::BoundedBytes(choices),
716 ] = &fields[..]
717 {
718 return Prng::Seeded {
719 choices: choices.to_vec(),
720 uplc: Data::constr(
721 Prng::SEEDED,
722 vec![
723 PlutusData::BoundedBytes(bytes.to_owned()),
724 PlutusData::BoundedBytes(vec![].into()),
727 ],
728 ),
729 };
730 }
731 }
732
733 if *tag == 121 + Prng::REPLAYED {
734 if let [PlutusData::BigInt(..), PlutusData::BoundedBytes(choices)] = &fields[..]
735 {
736 return Prng::Replayed {
737 choices: choices.to_vec(),
738 uplc: cst.clone(),
739 };
740 }
741 }
742 }
743
744 unreachable!("malformed Prng: {cst:#?}")
745 }
746
747 if let Term::Constant(rc) = &result {
748 if let Constant::Data(PlutusData::Constr(Constr { tag, fields, .. })) = &rc.borrow() {
749 if *tag == 121 + Prng::SOME {
750 if let [PlutusData::Array(elems)] = &fields[..] {
751 if let [new_seed, value] = &elems[..] {
752 return Some((as_prng(new_seed), value.clone()));
753 }
754 }
755 }
756
757 if *tag == 121 + Prng::NONE {
762 return None;
763 }
764 }
765 }
766
767 unreachable!("Fuzzer yielded a malformed result? {result:#?}")
768 }
769}
770
771pub struct Counterexample<'a> {
778 pub value: PlutusData,
779 pub choices: Vec<u8>,
780 pub cache: Cache<'a, PlutusData>,
781}
782
783impl Counterexample<'_> {
784 fn consider(&mut self, choices: &[u8]) -> bool {
785 if choices == self.choices {
786 return true;
787 }
788
789 match self.cache.get(choices) {
790 Status::Invalid | Status::Ignore => false,
791 Status::Keep(value) => {
792 if choices.len() <= self.choices.len() || choices < &self.choices[..] {
795 self.value = value;
796 self.choices = choices.to_vec();
797 true
798 } else {
799 false
800 }
801 }
802 }
803 }
804
805 pub fn simplify(&mut self) {
826 let mut prev;
827
828 let mut steps = 0;
829
830 #[cfg(not(target_family = "wasm"))]
831 let now = std::time::Instant::now();
832
833 eprintln!(
834 "{}",
835 Event::Simplifying {
836 choices: self.choices.len(),
837 }
838 );
839
840 loop {
841 prev = self.choices.clone();
842
843 let mut k = 8;
848 while k > 0 {
849 let (mut i, mut underflow) = if self.choices.len() < k {
850 (0, true)
851 } else {
852 (self.choices.len() - k, false)
853 };
854
855 while !underflow {
856 if i >= self.choices.len() {
857 (i, underflow) = i.overflowing_sub(1);
858 steps += 1;
859 continue;
860 }
861
862 let j = i + k;
863
864 let mut choices = [
865 &self.choices[..i],
866 if j < self.choices.len() {
867 &self.choices[j..]
868 } else {
869 &[]
870 },
871 ]
872 .concat();
873
874 if !self.consider(&choices) {
875 if i > 0 && choices[i - 1] > 0 {
881 choices[i - 1] -= 1;
882 if self.consider(&choices) {
883 i += 1;
884 };
885 }
886
887 (i, underflow) = i.overflowing_sub(1);
888 }
889
890 steps += 1;
891 }
892
893 k /= 2
894 }
895
896 if !self.choices.is_empty() {
897 let mut k = 8;
901 while k > 1 {
902 let mut i = self.choices.len();
903 while i >= k {
904 steps += 1;
905 let ivs = (i - k..i).map(|j| (j, 0)).collect::<Vec<_>>();
906 i -= if self.replace(ivs) { k } else { 1 }
907 }
908 k /= 2
909 }
910
911 let (mut i, mut underflow) = (self.choices.len() - 1, false);
915 while !underflow {
916 steps += 1;
917 self.binary_search_replace(0, self.choices[i], |v| vec![(i, v)]);
918 (i, underflow) = i.overflowing_sub(1);
919 }
920
921 let mut k = 8;
923 while k > 1 {
924 let mut i = self.choices.len() - 1;
925 while i >= k {
926 steps += 1;
927 let (from, to) = (i - k, i);
928 self.replace(
929 (from..to)
930 .zip(self.choices[from..to].iter().cloned().sorted())
931 .collect(),
932 );
933 i -= 1;
934 }
935 k /= 2
936 }
937
938 for k in [2, 1] {
943 let mut j = self.choices.len() - 1;
944 while j >= k {
945 let i = j - k;
946
947 if self.choices[i] > self.choices[j] {
949 self.replace(vec![(i, self.choices[j]), (j, self.choices[i])]);
950 }
951
952 let iv = self.choices[i];
953 let jv = self.choices[j];
954
955 if iv > 0 && jv <= u8::MAX - iv {
957 self.binary_search_replace(0, iv, |v| vec![(i, v), (j, jv + (iv - v))]);
958 }
959
960 steps += 1;
961
962 j -= 1
963 }
964 }
965 }
966
967 if prev.as_slice() == self.choices.as_slice() {
970 break;
971 }
972 }
973
974 eprintln!(
975 "{}",
976 Event::Simplified {
977 #[cfg(not(target_family = "wasm"))]
978 duration: now.elapsed(),
979 #[cfg(target_family = "wasm")]
980 duration: (),
981 steps,
982 }
983 );
984 }
985
986 fn binary_search_replace<F>(&mut self, lo: u8, hi: u8, f: F) -> u8
989 where
990 F: Fn(u8) -> Vec<(usize, u8)>,
991 {
992 if self.replace(f(lo)) {
993 return lo;
994 }
995
996 let mut lo = lo;
997 let mut hi = hi;
998
999 while lo + 1 < hi {
1000 let mid = lo + (hi - lo) / 2;
1001 if self.replace(f(mid)) {
1002 hi = mid;
1003 } else {
1004 lo = mid;
1005 }
1006 }
1007
1008 hi
1009 }
1010
1011 fn replace(&mut self, ivs: Vec<(usize, u8)>) -> bool {
1014 let mut choices = self.choices.clone();
1015
1016 for (i, v) in ivs {
1017 if i >= choices.len() {
1018 return false;
1019 }
1020 choices[i] = v;
1021 }
1022
1023 self.consider(&choices)
1024 }
1025}
1026
1027pub struct Cache<'a, T> {
1039 db: PatriciaMap<Status<T>>,
1040 #[allow(clippy::type_complexity)]
1041 run: Box<dyn Fn(&[u8]) -> Status<T> + 'a>,
1042}
1043
1044#[derive(Debug, Clone, Copy, PartialEq)]
1045pub enum Status<T> {
1046 Keep(T),
1047 Ignore,
1048 Invalid,
1049}
1050
1051impl<'a, T> Cache<'a, T>
1052where
1053 T: PartialEq + Clone,
1054{
1055 pub fn new<F>(run: F) -> Cache<'a, T>
1056 where
1057 F: Fn(&[u8]) -> Status<T> + 'a,
1058 {
1059 Cache {
1060 db: PatriciaMap::new(),
1061 run: Box::new(run),
1062 }
1063 }
1064
1065 pub fn size(&self) -> usize {
1066 self.db.len()
1067 }
1068
1069 pub fn get(&mut self, choices: &[u8]) -> Status<T> {
1070 if let Some((prefix, status)) = self.db.get_longest_common_prefix(choices) {
1071 let status = status.clone();
1072 if status != Status::Invalid || prefix == choices {
1073 return status;
1074 }
1075 }
1076
1077 let status = self.run.deref()(choices);
1078
1079 if status != Status::Invalid {
1085 let keys = self
1086 .db
1087 .iter_prefix(choices)
1088 .map(|(k, _)| k)
1089 .collect::<Vec<_>>();
1090 for k in keys {
1091 self.db.remove(k);
1092 }
1093 }
1094
1095 self.db.insert(choices, status.clone());
1096
1097 status
1098 }
1099}
1100
1101#[derive(Debug, Clone)]
1108pub enum TestResult<U, T> {
1109 UnitTestResult(UnitTestResult<U>),
1110 PropertyTestResult(PropertyTestResult<T>),
1111 BenchmarkResult(BenchmarkResult),
1112}
1113
1114unsafe impl<U, T> Send for TestResult<U, T> {}
1115
1116impl TestResult<(Constant, Rc<Type>), PlutusData> {
1117 pub fn reify(
1118 self,
1119 data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
1120 ) -> TestResult<UntypedExpr, UntypedExpr> {
1121 match self {
1122 TestResult::UnitTestResult(test) => TestResult::UnitTestResult(test.reify(data_types)),
1123 TestResult::PropertyTestResult(test) => {
1124 TestResult::PropertyTestResult(test.reify(data_types))
1125 }
1126 TestResult::BenchmarkResult(result) => TestResult::BenchmarkResult(result),
1127 }
1128 }
1129}
1130
1131impl<U, T> TestResult<U, T> {
1132 pub fn is_success(&self) -> bool {
1133 match self {
1134 TestResult::UnitTestResult(UnitTestResult { success, .. }) => *success,
1135 TestResult::PropertyTestResult(PropertyTestResult {
1136 counterexample: Err(..),
1137 ..
1138 }) => false,
1139 TestResult::PropertyTestResult(PropertyTestResult {
1140 counterexample: Ok(counterexample),
1141 test,
1142 ..
1143 }) => match test.on_test_failure {
1144 OnTestFailure::FailImmediately | OnTestFailure::SucceedEventually => {
1145 counterexample.is_none()
1146 }
1147 OnTestFailure::SucceedImmediately => counterexample.is_some(),
1148 },
1149 TestResult::BenchmarkResult(BenchmarkResult { error, .. }) => error.is_none(),
1150 }
1151 }
1152
1153 pub fn module(&self) -> &str {
1154 match self {
1155 TestResult::UnitTestResult(UnitTestResult { test, .. }) => test.module.as_str(),
1156 TestResult::PropertyTestResult(PropertyTestResult { test, .. }) => test.module.as_str(),
1157 TestResult::BenchmarkResult(BenchmarkResult { bench, .. }) => bench.module.as_str(),
1158 }
1159 }
1160
1161 pub fn title(&self) -> &str {
1162 match self {
1163 TestResult::UnitTestResult(UnitTestResult { test, .. }) => test.name.as_str(),
1164 TestResult::PropertyTestResult(PropertyTestResult { test, .. }) => test.name.as_str(),
1165 TestResult::BenchmarkResult(BenchmarkResult { bench, .. }) => bench.name.as_str(),
1166 }
1167 }
1168
1169 pub fn logs(&self) -> &[String] {
1170 match self {
1171 TestResult::UnitTestResult(UnitTestResult { logs, .. })
1172 | TestResult::PropertyTestResult(PropertyTestResult { logs, .. }) => logs,
1173 TestResult::BenchmarkResult(BenchmarkResult { error, .. }) => {
1174 error.as_ref().map(|e| e.logs()).unwrap_or_default()
1175 }
1176 }
1177 }
1178}
1179
1180#[derive(Debug, Clone)]
1181pub struct UnitTestResult<T> {
1182 pub success: bool,
1183 pub spent_budget: ExBudget,
1184 pub logs: Vec<String>,
1185 pub test: UnitTest,
1186 pub assertion: Option<Assertion<T>>,
1187}
1188
1189unsafe impl<T> Send for UnitTestResult<T> {}
1190
1191impl UnitTestResult<(Constant, Rc<Type>)> {
1192 pub fn reify(
1193 self,
1194 data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
1195 ) -> UnitTestResult<UntypedExpr> {
1196 UnitTestResult {
1197 success: self.success,
1198 spent_budget: self.spent_budget,
1199 logs: self.logs,
1200 test: self.test,
1201 assertion: self.assertion.and_then(|assertion| {
1202 if self.success {
1205 return None;
1206 }
1207
1208 Some(Assertion {
1209 bin_op: assertion.bin_op,
1210 head: assertion.head.map(|(cst, tipo)| {
1211 UntypedExpr::reify_constant(data_types, cst, tipo)
1212 .expect("failed to reify assertion operand?")
1213 }),
1214 tail: assertion.tail.map(|xs| {
1215 xs.mapped(|(cst, tipo)| {
1216 UntypedExpr::reify_constant(data_types, cst, tipo)
1217 .expect("failed to reify assertion operand?")
1218 })
1219 }),
1220 })
1221 }),
1222 }
1223 }
1224}
1225
1226#[derive(Debug, Clone)]
1227pub struct PropertyTestResult<T> {
1228 pub test: PropertyTest,
1229 pub counterexample: Result<Option<T>, uplc::machine::Error>,
1230 pub iterations: usize,
1231 pub labels: BTreeMap<String, usize>,
1232 pub logs: Vec<String>,
1233}
1234
1235unsafe impl<T> Send for PropertyTestResult<T> {}
1236
1237impl PropertyTestResult<PlutusData> {
1238 pub fn reify(
1239 self,
1240 data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
1241 ) -> PropertyTestResult<UntypedExpr> {
1242 PropertyTestResult {
1243 counterexample: self.counterexample.map(|ok| {
1244 ok.map(|counterexample| {
1245 UntypedExpr::reify_data(
1246 data_types,
1247 counterexample,
1248 self.test.fuzzer.type_info.clone(),
1249 )
1250 .expect("failed to reify counterexample?")
1251 })
1252 }),
1253 iterations: self.iterations,
1254 test: self.test,
1255 labels: self.labels,
1256 logs: self.logs,
1257 }
1258 }
1259}
1260
1261#[derive(Debug, Clone)]
1262pub struct Assertion<T> {
1263 pub bin_op: BinOp,
1264 pub head: Result<T, ()>,
1265 pub tail: Result<Vec1<T>, ()>,
1266}
1267
1268impl TryFrom<TypedExpr> for Assertion<TypedExpr> {
1269 type Error = ();
1270
1271 fn try_from(body: TypedExpr) -> Result<Self, Self::Error> {
1272 match body {
1273 TypedExpr::BinOp {
1274 name,
1275 tipo,
1276 left,
1277 right,
1278 ..
1279 } if tipo == Type::bool() => {
1280 match (*right).clone().try_into() {
1282 Ok(Assertion {
1283 bin_op,
1284 head: Ok(head),
1285 tail: Ok(tail),
1286 ..
1287 }) if bin_op == name => {
1288 let mut both = vec1![head];
1289 both.extend(tail);
1290 Ok(Assertion {
1291 bin_op: name,
1292 head: Ok(*left),
1293 tail: Ok(both),
1294 })
1295 }
1296 _ => Ok(Assertion {
1297 bin_op: name,
1298 head: Ok(*left),
1299 tail: Ok(vec1![*right]),
1300 }),
1301 }
1302 }
1303
1304 TypedExpr::If {
1306 branches,
1307 final_else,
1308 ..
1309 } => {
1310 if let [
1311 IfBranch {
1312 condition, body, ..
1313 },
1314 ] = &branches[..]
1315 {
1316 let then_is_true = match body {
1317 TypedExpr::Var {
1318 name, constructor, ..
1319 } => name == "True" && constructor.tipo == Type::bool(),
1320 _ => false,
1321 };
1322
1323 let else_is_wrapped_false = match *final_else {
1324 TypedExpr::Trace { then, .. } => match *then {
1325 TypedExpr::Var {
1326 name, constructor, ..
1327 } => name == "False" && constructor.tipo == Type::bool(),
1328 _ => false,
1329 },
1330 _ => false,
1331 };
1332
1333 if then_is_true && else_is_wrapped_false {
1334 return condition.to_owned().try_into();
1335 }
1336 }
1337
1338 Err(())
1339 }
1340
1341 TypedExpr::Trace { then, .. } => (*then).try_into(),
1342
1343 TypedExpr::Sequence { expressions, .. } | TypedExpr::Pipeline { expressions, .. } => {
1344 if let Ok(Assertion {
1345 bin_op,
1346 head: Ok(head),
1347 tail: Ok(tail),
1348 }) = expressions.last().unwrap().to_owned().try_into()
1349 {
1350 let replace = |expr| {
1351 let mut expressions = expressions.clone();
1352 expressions.pop();
1353 expressions.push(expr);
1354 TypedExpr::Sequence {
1355 expressions,
1356 location: Span::empty(),
1357 }
1358 };
1359
1360 Ok(Assertion {
1361 bin_op,
1362 head: Ok(replace(head)),
1363 tail: Ok(tail.mapped(replace)),
1364 })
1365 } else {
1366 Err(())
1367 }
1368 }
1369 _ => Err(()),
1370 }
1371 }
1372}
1373
1374pub struct AssertionStyleOptions<'a> {
1375 red: Box<dyn Fn(String) -> String + 'a>,
1376 bold: Box<dyn Fn(String) -> String + 'a>,
1377}
1378
1379impl<'a> AssertionStyleOptions<'a> {
1380 pub fn new(stream: Option<&'a Stream>) -> Self {
1381 match stream {
1382 Some(stream) => Self {
1383 red: Box::new(|s| {
1384 s.if_supports_color(stream.to_owned(), |s| s.red())
1385 .to_string()
1386 }),
1387 bold: Box::new(|s| {
1388 s.if_supports_color(stream.to_owned(), |s| s.bold())
1389 .to_string()
1390 }),
1391 },
1392 None => Self {
1393 red: Box::new(|s| s),
1394 bold: Box::new(|s| s),
1395 },
1396 }
1397 }
1398}
1399
1400impl Assertion<UntypedExpr> {
1401 #[allow(clippy::just_underscores_and_digits)]
1402 pub fn to_string(&self, expect_failure: bool, style: &AssertionStyleOptions) -> String {
1403 let red = |s: &str| style.red.as_ref()(s.to_string());
1404 let x = |s: &str| style.red.as_ref()(style.bold.as_ref()(format!("× {s}")));
1405
1406 if self.head.is_err() {
1408 return x("program failed");
1409 }
1410
1411 if self.tail.is_err() {
1413 return x("program failed");
1414 }
1415
1416 fn fmt_side(side: &UntypedExpr, red: &dyn Fn(&str) -> String) -> String {
1417 let __ = red("│");
1418
1419 Formatter::new()
1420 .expr(side, false)
1421 .to_pretty_string(60)
1422 .lines()
1423 .map(|line| format!("{__} {line}"))
1424 .collect::<Vec<String>>()
1425 .join("\n")
1426 }
1427
1428 let left = fmt_side(self.head.as_ref().unwrap(), &red);
1429
1430 let tail = self.tail.as_ref().unwrap();
1431
1432 let right = fmt_side(tail.first(), &red);
1433
1434 format!(
1435 "{}{}{}",
1436 x("expected"),
1437 if expect_failure && self.bin_op == BinOp::Or {
1438 x(" neither\n")
1439 } else {
1440 "\n".to_string()
1441 },
1442 if expect_failure {
1443 match self.bin_op {
1444 BinOp::And => [
1445 left,
1446 x("and"),
1447 [
1448 tail.mapped_ref(|s| fmt_side(s, &red))
1449 .join(format!("\n{}\n", x("and")).as_str()),
1450 if tail.len() > 1 {
1451 x("to not all be true")
1452 } else {
1453 x("to not both be true")
1454 },
1455 ]
1456 .join("\n"),
1457 ],
1458 BinOp::Or => [
1459 left,
1460 x("nor"),
1461 [
1462 tail.mapped_ref(|s| fmt_side(s, &red))
1463 .join(format!("\n{}\n", x("nor")).as_str()),
1464 x("to be true"),
1465 ]
1466 .join("\n"),
1467 ],
1468 BinOp::Eq => [left, x("to not equal"), right],
1469 BinOp::NotEq => [left, x("to not be different"), right],
1470 BinOp::LtInt => [left, x("to not be lower than"), right],
1471 BinOp::LtEqInt => [left, x("to not be lower than or equal to"), right],
1472 BinOp::GtInt => [left, x("to not be greater than"), right],
1473 BinOp::GtEqInt => [left, x("to not be greater than or equal to"), right],
1474 _ => unreachable!("unexpected non-boolean binary operator in assertion?"),
1475 }
1476 .join("\n")
1477 } else {
1478 match self.bin_op {
1479 BinOp::And => [
1480 left,
1481 x("and"),
1482 [
1483 tail.mapped_ref(|s| fmt_side(s, &red))
1484 .join(format!("\n{}\n", x("and")).as_str()),
1485 if tail.len() > 1 {
1486 x("to all be true")
1487 } else {
1488 x("to both be true")
1489 },
1490 ]
1491 .join("\n"),
1492 ],
1493 BinOp::Or => [
1494 left,
1495 x("or"),
1496 [
1497 tail.mapped_ref(|s| fmt_side(s, &red))
1498 .join(format!("\n{}\n", x("or")).as_str()),
1499 x("to be true"),
1500 ]
1501 .join("\n"),
1502 ],
1503 BinOp::Eq => [left, x("to equal"), right],
1504 BinOp::NotEq => [left, x("to not equal"), right],
1505 BinOp::LtInt => [left, x("to be lower than"), right],
1506 BinOp::LtEqInt => [left, x("to be lower than or equal to"), right],
1507 BinOp::GtInt => [left, x("to be greater than"), right],
1508 BinOp::GtEqInt => [left, x("to be greater than or equal to"), right],
1509 _ => unreachable!("unexpected non-boolean binary operator in assertion?"),
1510 }
1511 .join("\n")
1512 }
1513 )
1514 }
1515}
1516
1517#[derive(Debug, Clone)]
1518pub struct BenchmarkResult {
1519 pub bench: Benchmark,
1520 pub measures: Vec<(usize, ExBudget)>,
1521 pub error: Option<BenchmarkError>,
1522}
1523
1524unsafe impl Send for BenchmarkResult {}
1525unsafe impl Sync for BenchmarkResult {}
1526
1527#[cfg(test)]
1528mod test {
1529 use super::*;
1530
1531 #[test]
1532 fn test_cache() {
1533 let called = std::cell::RefCell::new(0);
1534
1535 let mut cache = Cache::new(|choices| {
1536 called.replace_with(|n| *n + 1);
1537
1538 match choices {
1539 [0, 0, 0] => Status::Keep(true),
1540 _ => {
1541 if choices.len() <= 2 {
1542 Status::Invalid
1543 } else {
1544 Status::Ignore
1545 }
1546 }
1547 }
1548 });
1549
1550 assert_eq!(cache.get(&[1, 1]), Status::Invalid); assert_eq!(cache.get(&[1, 1, 2, 3]), Status::Ignore); assert_eq!(cache.get(&[1, 1, 2]), Status::Ignore); assert_eq!(cache.get(&[1, 1, 2, 2]), Status::Ignore); assert_eq!(cache.get(&[1, 1, 2, 1]), Status::Ignore); assert_eq!(cache.get(&[0, 1, 2]), Status::Ignore); assert_eq!(cache.get(&[0, 0, 0]), Status::Keep(true)); assert_eq!(cache.get(&[0, 0, 0]), Status::Keep(true)); assert_eq!(called.borrow().deref().to_owned(), 5, "execution calls");
1560 assert_eq!(cache.size(), 4, "cache size");
1561 }
1562}