1use crate::{
4 MultiContractRunner,
5 fuzz::{BaseCounterExample, FuzzedCases},
6 gas_report::GasReport,
7};
8use alloy_primitives::{
9 Address, Log,
10 map::{AddressHashMap, HashMap},
11};
12use eyre::Report;
13use foundry_common::{get_contract_name, get_file_name, shell};
14use foundry_evm::{
15 core::Breakpoints,
16 coverage::HitMaps,
17 decode::SkipReason,
18 executors::{RawCallResult, invariant::InvariantMetrics},
19 fuzz::{CounterExample, FuzzCase, FuzzFixtures, FuzzTestResult},
20 traces::{CallTraceArena, CallTraceDecoder, TraceKind, Traces},
21};
22use serde::{Deserialize, Serialize};
23use std::{
24 collections::{BTreeMap, HashMap as Map},
25 fmt::{self, Write},
26 time::Duration,
27};
28use yansi::Paint;
29
30#[derive(Clone, Debug)]
32pub struct TestOutcome {
33 pub results: BTreeMap<String, SuiteResult>,
37 pub allow_failure: bool,
39 pub last_run_decoder: Option<CallTraceDecoder>,
45 pub gas_report: Option<GasReport>,
47 pub runner: Option<MultiContractRunner>,
49}
50
51impl TestOutcome {
52 pub fn new(
54 runner: Option<MultiContractRunner>,
55 results: BTreeMap<String, SuiteResult>,
56 allow_failure: bool,
57 ) -> Self {
58 Self { results, allow_failure, last_run_decoder: None, gas_report: None, runner }
59 }
60
61 pub fn empty(runner: Option<MultiContractRunner>, allow_failure: bool) -> Self {
63 Self::new(runner, BTreeMap::new(), allow_failure)
64 }
65
66 pub fn successes(&self) -> impl Iterator<Item = (&String, &TestResult)> {
68 self.tests().filter(|(_, t)| t.status.is_success())
69 }
70
71 pub fn skips(&self) -> impl Iterator<Item = (&String, &TestResult)> {
73 self.tests().filter(|(_, t)| t.status.is_skipped())
74 }
75
76 pub fn failures(&self) -> impl Iterator<Item = (&String, &TestResult)> {
78 self.tests().filter(|(_, t)| t.status.is_failure())
79 }
80
81 pub fn tests(&self) -> impl Iterator<Item = (&String, &TestResult)> {
83 self.results.values().flat_map(|suite| suite.tests())
84 }
85
86 pub fn into_tests_cloned(&self) -> impl Iterator<Item = SuiteTestResult> + '_ {
89 self.results
90 .iter()
91 .flat_map(|(file, suite)| {
92 suite
93 .test_results
94 .iter()
95 .map(move |(sig, result)| (file.clone(), sig.clone(), result.clone()))
96 })
97 .map(|(artifact_id, signature, result)| SuiteTestResult {
98 artifact_id,
99 signature,
100 result,
101 })
102 }
103
104 pub fn into_tests(self) -> impl Iterator<Item = SuiteTestResult> {
106 self.results
107 .into_iter()
108 .flat_map(|(file, suite)| {
109 suite.test_results.into_iter().map(move |t| (file.clone(), t))
110 })
111 .map(|(artifact_id, (signature, result))| SuiteTestResult {
112 artifact_id,
113 signature,
114 result,
115 })
116 }
117
118 pub fn passed(&self) -> usize {
120 self.successes().count()
121 }
122
123 pub fn skipped(&self) -> usize {
125 self.skips().count()
126 }
127
128 pub fn failed(&self) -> usize {
130 self.failures().count()
131 }
132
133 pub fn total_time(&self) -> Duration {
137 self.results.values().map(|suite| suite.duration).sum()
138 }
139
140 pub fn summary(&self, wall_clock_time: Duration) -> String {
142 let num_test_suites = self.results.len();
143 let suites = if num_test_suites == 1 { "suite" } else { "suites" };
144 let total_passed = self.passed();
145 let total_failed = self.failed();
146 let total_skipped = self.skipped();
147 let total_tests = total_passed + total_failed + total_skipped;
148 format!(
149 "\nRan {} test {} in {:.2?} ({:.2?} CPU time): {} tests passed, {} failed, {} skipped ({} total tests)",
150 num_test_suites,
151 suites,
152 wall_clock_time,
153 self.total_time(),
154 total_passed.green(),
155 total_failed.red(),
156 total_skipped.yellow(),
157 total_tests
158 )
159 }
160
161 pub fn ensure_ok(&self, silent: bool) -> eyre::Result<()> {
163 let outcome = self;
164 let failures = outcome.failures().count();
165 if outcome.allow_failure || failures == 0 {
166 return Ok(());
167 }
168
169 if shell::is_quiet() || silent {
170 std::process::exit(1);
171 }
172
173 sh_println!("\nFailing tests:")?;
174 for (suite_name, suite) in &outcome.results {
175 let failed = suite.failed();
176 if failed == 0 {
177 continue;
178 }
179
180 let term = if failed > 1 { "tests" } else { "test" };
181 sh_println!("Encountered {failed} failing {term} in {suite_name}")?;
182 for (name, result) in suite.failures() {
183 sh_println!("{}", result.short_result(name))?;
184 }
185 sh_println!()?;
186 }
187 let successes = outcome.passed();
188 sh_println!(
189 "Encountered a total of {} failing tests, {} tests succeeded",
190 failures.to_string().red(),
191 successes.to_string().green()
192 )?;
193
194 let test_word = if failures == 1 { "test" } else { "tests" };
196 sh_println!(
197 "\nTip: Run {} to retry only the {} failed {}",
198 "`forge test --rerun`".cyan(),
199 failures,
200 test_word
201 )?;
202
203 std::process::exit(1);
204 }
205
206 pub fn remove_first(&mut self) -> Option<(String, String, TestResult)> {
208 self.results.iter_mut().find_map(|(suite_name, suite)| {
209 if let Some(test_name) = suite.test_results.keys().next().cloned() {
210 let result = suite.test_results.remove(&test_name).unwrap();
211 Some((suite_name.clone(), test_name, result))
212 } else {
213 None
214 }
215 })
216 }
217}
218
219#[derive(Clone, Debug, Serialize)]
221pub struct SuiteResult {
222 #[serde(with = "foundry_common::serde_helpers::duration")]
224 pub duration: Duration,
225 pub test_results: BTreeMap<String, TestResult>,
227 pub warnings: Vec<String>,
229}
230
231impl SuiteResult {
232 pub fn new(
233 duration: Duration,
234 test_results: BTreeMap<String, TestResult>,
235 mut warnings: Vec<String>,
236 ) -> Self {
237 let mut deprecated_cheatcodes = HashMap::new();
239 for test_result in test_results.values() {
240 deprecated_cheatcodes.extend(test_result.deprecated_cheatcodes.clone());
241 }
242 if !deprecated_cheatcodes.is_empty() {
243 let mut warning =
244 "the following cheatcode(s) are deprecated and will be removed in future versions:"
245 .to_string();
246 for (cheatcode, reason) in deprecated_cheatcodes {
247 write!(warning, "\n {cheatcode}").unwrap();
248 if let Some(reason) = reason {
249 write!(warning, ": {reason}").unwrap();
250 }
251 }
252 warnings.push(warning);
253 }
254
255 Self { duration, test_results, warnings }
256 }
257
258 pub fn successes(&self) -> impl Iterator<Item = (&String, &TestResult)> {
260 self.tests().filter(|(_, t)| t.status.is_success())
261 }
262
263 pub fn skips(&self) -> impl Iterator<Item = (&String, &TestResult)> {
265 self.tests().filter(|(_, t)| t.status.is_skipped())
266 }
267
268 pub fn failures(&self) -> impl Iterator<Item = (&String, &TestResult)> {
270 self.tests().filter(|(_, t)| t.status.is_failure())
271 }
272
273 pub fn passed(&self) -> usize {
275 self.successes().count()
276 }
277
278 pub fn skipped(&self) -> usize {
280 self.skips().count()
281 }
282
283 pub fn failed(&self) -> usize {
285 self.failures().count()
286 }
287
288 pub fn tests(&self) -> impl Iterator<Item = (&String, &TestResult)> {
290 self.test_results.iter()
291 }
292
293 pub fn is_empty(&self) -> bool {
295 self.test_results.is_empty()
296 }
297
298 pub fn len(&self) -> usize {
300 self.test_results.len()
301 }
302
303 pub fn total_time(&self) -> Duration {
307 self.test_results.values().map(|result| result.duration).sum()
308 }
309
310 pub fn summary(&self) -> String {
312 let failed = self.failed();
313 let result = if failed == 0 { "ok".green() } else { "FAILED".red() };
314 format!(
315 "Suite result: {}. {} passed; {} failed; {} skipped; finished in {:.2?} ({:.2?} CPU time)",
316 result,
317 self.passed().green(),
318 failed.red(),
319 self.skipped().yellow(),
320 self.duration,
321 self.total_time(),
322 )
323 }
324}
325
326#[derive(Clone, Debug)]
330pub struct SuiteTestResult {
331 pub artifact_id: String,
334 pub signature: String,
336 pub result: TestResult,
338}
339
340impl SuiteTestResult {
341 pub fn gas_used(&self) -> u64 {
343 self.result.kind.report().gas()
344 }
345
346 pub fn contract_name(&self) -> &str {
348 get_contract_name(&self.artifact_id)
349 }
350
351 pub fn file_name(&self) -> &str {
353 get_file_name(&self.artifact_id)
354 }
355}
356
357#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
359pub enum TestStatus {
360 Success,
361 #[default]
362 Failure,
363 Skipped,
364}
365
366impl TestStatus {
367 #[inline]
369 pub fn is_success(self) -> bool {
370 matches!(self, Self::Success)
371 }
372
373 #[inline]
375 pub fn is_failure(self) -> bool {
376 matches!(self, Self::Failure)
377 }
378
379 #[inline]
381 pub fn is_skipped(self) -> bool {
382 matches!(self, Self::Skipped)
383 }
384}
385
386#[derive(Clone, Debug, Default, Serialize, Deserialize)]
388pub struct TestResult {
389 pub status: TestStatus,
394
395 pub reason: Option<String>,
398
399 pub counterexample: Option<CounterExample>,
401
402 pub logs: Vec<Log>,
405
406 pub decoded_logs: Vec<String>,
409
410 pub kind: TestKind,
412
413 pub traces: Traces,
415
416 #[serde(skip)]
420 pub gas_report_traces: Vec<Vec<CallTraceArena>>,
421
422 #[serde(skip)]
424 pub line_coverage: Option<HitMaps>,
425
426 #[serde(rename = "labeled_addresses")] pub labels: AddressHashMap<String>,
429
430 #[serde(with = "foundry_common::serde_helpers::duration")]
431 pub duration: Duration,
432
433 pub breakpoints: Breakpoints,
435
436 pub gas_snapshots: BTreeMap<String, BTreeMap<String, String>>,
438
439 #[serde(skip)]
441 pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>,
442}
443
444impl fmt::Display for TestResult {
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446 match self.status {
447 TestStatus::Success => "[PASS]".green().fmt(f),
448 TestStatus::Skipped => {
449 let mut s = String::from("[SKIP");
450 if let Some(reason) = &self.reason {
451 write!(s, ": {reason}").unwrap();
452 }
453 s.push(']');
454 s.yellow().fmt(f)
455 }
456 TestStatus::Failure => {
457 let mut s = String::from("[FAIL");
458 if self.reason.is_some() || self.counterexample.is_some() {
459 if let Some(reason) = &self.reason {
460 write!(s, ": {reason}").unwrap();
461 }
462
463 if let Some(counterexample) = &self.counterexample {
464 match counterexample {
465 CounterExample::Single(ex) => {
466 write!(s, "; counterexample: {ex}]").unwrap();
467 }
468 CounterExample::Sequence(original, sequence) => {
469 s.push_str(
470 format!(
471 "]\n\t[Sequence] (original: {original}, shrunk: {})\n",
472 sequence.len()
473 )
474 .as_str(),
475 );
476 for ex in sequence {
477 writeln!(s, "{ex}").unwrap();
478 }
479 }
480 }
481 } else {
482 s.push(']');
483 }
484 } else {
485 s.push(']');
486 }
487 s.red().wrap().fmt(f)
488 }
489 }
490 }
491}
492
493macro_rules! extend {
494 ($a:expr, $b:expr, $trace_kind:expr) => {
495 $a.logs.extend($b.logs);
496 $a.labels.extend($b.labels);
497 $a.traces.extend($b.traces.map(|traces| ($trace_kind, traces)));
498 $a.merge_coverages($b.line_coverage);
499 };
500}
501
502impl TestResult {
503 pub fn new(setup: &TestSetup) -> Self {
505 Self {
506 labels: setup.labels.clone(),
507 logs: setup.logs.clone(),
508 traces: setup.traces.clone(),
509 line_coverage: setup.coverage.clone(),
510 ..Default::default()
511 }
512 }
513
514 pub fn fail(reason: String) -> Self {
516 Self { status: TestStatus::Failure, reason: Some(reason), ..Default::default() }
517 }
518
519 pub fn setup_result(setup: TestSetup) -> Self {
521 let TestSetup {
522 address: _,
523 fuzz_fixtures: _,
524 logs,
525 labels,
526 traces,
527 coverage,
528 deployed_libs: _,
529 reason,
530 skipped,
531 deployment_failure: _,
532 } = setup;
533 Self {
534 status: if skipped { TestStatus::Skipped } else { TestStatus::Failure },
535 reason,
536 logs,
537 traces,
538 line_coverage: coverage,
539 labels,
540 ..Default::default()
541 }
542 }
543
544 pub fn single_skip(&mut self, reason: SkipReason) {
546 self.status = TestStatus::Skipped;
547 self.reason = reason.0;
548 }
549
550 pub fn single_fail(&mut self, reason: Option<String>) {
552 self.status = TestStatus::Failure;
553 self.reason = reason;
554 }
555
556 pub fn single_result(
559 &mut self,
560 success: bool,
561 reason: Option<String>,
562 raw_call_result: RawCallResult,
563 ) {
564 self.kind =
565 TestKind::Unit { gas: raw_call_result.gas_used.wrapping_sub(raw_call_result.stipend) };
566
567 extend!(self, raw_call_result, TraceKind::Execution);
568
569 self.status = match success {
570 true => TestStatus::Success,
571 false => TestStatus::Failure,
572 };
573 self.reason = reason;
574 self.duration = Duration::default();
575 self.gas_report_traces = Vec::new();
576
577 if let Some(cheatcodes) = raw_call_result.cheatcodes {
578 self.breakpoints = cheatcodes.breakpoints;
579 self.gas_snapshots = cheatcodes.gas_snapshots;
580 self.deprecated_cheatcodes = cheatcodes.deprecated;
581 }
582 }
583
584 pub fn fuzz_result(&mut self, result: FuzzTestResult) {
587 self.kind = TestKind::Fuzz {
588 median_gas: result.median_gas(false),
589 mean_gas: result.mean_gas(false),
590 first_case: result.first_case,
591 runs: result.gas_by_case.len(),
592 failed_corpus_replays: result.failed_corpus_replays,
593 };
594
595 extend!(self, result, TraceKind::Execution);
597
598 self.status = if result.skipped {
599 TestStatus::Skipped
600 } else if result.success {
601 TestStatus::Success
602 } else {
603 TestStatus::Failure
604 };
605 self.reason = result.reason;
606 self.counterexample = result.counterexample;
607 self.duration = Duration::default();
608 self.gas_report_traces = result.gas_report_traces.into_iter().map(|t| vec![t]).collect();
609 self.breakpoints = result.breakpoints.unwrap_or_default();
610 self.deprecated_cheatcodes = result.deprecated_cheatcodes;
611 }
612
613 pub fn fuzz_setup_fail(&mut self, e: Report) {
615 self.kind = TestKind::Fuzz {
616 first_case: Default::default(),
617 runs: 0,
618 mean_gas: 0,
619 median_gas: 0,
620 failed_corpus_replays: 0,
621 };
622 self.status = TestStatus::Failure;
623 self.reason = Some(format!("failed to set up fuzz testing environment: {e}"));
624 }
625
626 pub fn invariant_skip(&mut self, reason: SkipReason) {
628 self.kind = TestKind::Invariant {
629 runs: 1,
630 calls: 1,
631 reverts: 1,
632 metrics: HashMap::default(),
633 failed_corpus_replays: 0,
634 };
635 self.status = TestStatus::Skipped;
636 self.reason = reason.0;
637 }
638
639 pub fn invariant_replay_fail(
641 &mut self,
642 replayed_entirely: bool,
643 invariant_name: &String,
644 call_sequence: Vec<BaseCounterExample>,
645 ) {
646 self.kind = TestKind::Invariant {
647 runs: 1,
648 calls: 1,
649 reverts: 1,
650 metrics: HashMap::default(),
651 failed_corpus_replays: 0,
652 };
653 self.status = TestStatus::Failure;
654 self.reason = if replayed_entirely {
655 Some(format!("{invariant_name} replay failure"))
656 } else {
657 Some(format!("{invariant_name} persisted failure revert"))
658 };
659 self.counterexample = Some(CounterExample::Sequence(call_sequence.len(), call_sequence));
660 }
661
662 pub fn invariant_setup_fail(&mut self, e: Report) {
664 self.kind = TestKind::Invariant {
665 runs: 0,
666 calls: 0,
667 reverts: 0,
668 metrics: HashMap::default(),
669 failed_corpus_replays: 0,
670 };
671 self.status = TestStatus::Failure;
672 self.reason = Some(format!("failed to set up invariant testing environment: {e}"));
673 }
674
675 #[expect(clippy::too_many_arguments)]
677 pub fn invariant_result(
678 &mut self,
679 gas_report_traces: Vec<Vec<CallTraceArena>>,
680 success: bool,
681 reason: Option<String>,
682 counterexample: Option<CounterExample>,
683 cases: Vec<FuzzedCases>,
684 reverts: usize,
685 metrics: Map<String, InvariantMetrics>,
686 failed_corpus_replays: usize,
687 ) {
688 self.kind = TestKind::Invariant {
689 runs: cases.len(),
690 calls: cases.iter().map(|sequence| sequence.cases().len()).sum(),
691 reverts,
692 metrics,
693 failed_corpus_replays,
694 };
695 self.status = match success {
696 true => TestStatus::Success,
697 false => TestStatus::Failure,
698 };
699 self.reason = reason;
700 self.counterexample = counterexample;
701 self.gas_report_traces = gas_report_traces;
702 }
703
704 pub fn table_result(&mut self, result: FuzzTestResult) {
707 self.kind = TestKind::Table {
708 median_gas: result.median_gas(false),
709 mean_gas: result.mean_gas(false),
710 runs: result.gas_by_case.len(),
711 };
712
713 extend!(self, result, TraceKind::Execution);
715
716 self.status = if result.skipped {
717 TestStatus::Skipped
718 } else if result.success {
719 TestStatus::Success
720 } else {
721 TestStatus::Failure
722 };
723 self.reason = result.reason;
724 self.counterexample = result.counterexample;
725 self.duration = Duration::default();
726 self.gas_report_traces = result.gas_report_traces.into_iter().map(|t| vec![t]).collect();
727 self.breakpoints = result.breakpoints.unwrap_or_default();
728 self.deprecated_cheatcodes = result.deprecated_cheatcodes;
729 }
730
731 pub fn is_fuzz(&self) -> bool {
733 matches!(self.kind, TestKind::Fuzz { .. })
734 }
735
736 pub fn short_result(&self, name: &str) -> String {
738 format!("{self} {name} {}", self.kind.report())
739 }
740
741 pub fn extend(&mut self, call_result: RawCallResult) {
743 extend!(self, call_result, TraceKind::Execution);
744 }
745
746 pub fn merge_coverages(&mut self, other_coverage: Option<HitMaps>) {
748 HitMaps::merge_opt(&mut self.line_coverage, other_coverage);
749 }
750}
751
752#[derive(Clone, Debug, PartialEq, Eq)]
754pub enum TestKindReport {
755 Unit {
756 gas: u64,
757 },
758 Fuzz {
759 runs: usize,
760 mean_gas: u64,
761 median_gas: u64,
762 failed_corpus_replays: usize,
763 },
764 Invariant {
765 runs: usize,
766 calls: usize,
767 reverts: usize,
768 metrics: Map<String, InvariantMetrics>,
769 failed_corpus_replays: usize,
770 },
771 Table {
772 runs: usize,
773 mean_gas: u64,
774 median_gas: u64,
775 },
776}
777
778impl fmt::Display for TestKindReport {
779 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
780 match self {
781 Self::Unit { gas } => {
782 write!(f, "(gas: {gas})")
783 }
784 Self::Fuzz { runs, mean_gas, median_gas, failed_corpus_replays } => {
785 if *failed_corpus_replays != 0 {
786 write!(
787 f,
788 "(runs: {runs}, μ: {mean_gas}, ~: {median_gas}, failed corpus replays: {failed_corpus_replays})"
789 )
790 } else {
791 write!(f, "(runs: {runs}, μ: {mean_gas}, ~: {median_gas})")
792 }
793 }
794 Self::Invariant { runs, calls, reverts, metrics: _, failed_corpus_replays } => {
795 if *failed_corpus_replays != 0 {
796 write!(
797 f,
798 "(runs: {runs}, calls: {calls}, reverts: {reverts}, failed corpus replays: {failed_corpus_replays})"
799 )
800 } else {
801 write!(f, "(runs: {runs}, calls: {calls}, reverts: {reverts})")
802 }
803 }
804 Self::Table { runs, mean_gas, median_gas } => {
805 write!(f, "(runs: {runs}, μ: {mean_gas}, ~: {median_gas})")
806 }
807 }
808 }
809}
810
811impl TestKindReport {
812 pub fn gas(&self) -> u64 {
814 match *self {
815 Self::Unit { gas } => gas,
816 Self::Fuzz { median_gas, .. } | Self::Table { median_gas, .. } => median_gas,
818 Self::Invariant { .. } => 0,
820 }
821 }
822}
823
824#[derive(Clone, Debug, Serialize, Deserialize)]
826pub enum TestKind {
827 Unit { gas: u64 },
829 Fuzz {
831 first_case: FuzzCase,
833 runs: usize,
834 mean_gas: u64,
835 median_gas: u64,
836 failed_corpus_replays: usize,
837 },
838 Invariant {
840 runs: usize,
841 calls: usize,
842 reverts: usize,
843 metrics: Map<String, InvariantMetrics>,
844 failed_corpus_replays: usize,
845 },
846 Table { runs: usize, mean_gas: u64, median_gas: u64 },
848}
849
850impl Default for TestKind {
851 fn default() -> Self {
852 Self::Unit { gas: 0 }
853 }
854}
855
856impl TestKind {
857 pub fn report(&self) -> TestKindReport {
859 match self {
860 Self::Unit { gas } => TestKindReport::Unit { gas: *gas },
861 Self::Fuzz { first_case: _, runs, mean_gas, median_gas, failed_corpus_replays } => {
862 TestKindReport::Fuzz {
863 runs: *runs,
864 mean_gas: *mean_gas,
865 median_gas: *median_gas,
866 failed_corpus_replays: *failed_corpus_replays,
867 }
868 }
869 Self::Invariant { runs, calls, reverts, metrics: _, failed_corpus_replays } => {
870 TestKindReport::Invariant {
871 runs: *runs,
872 calls: *calls,
873 reverts: *reverts,
874 metrics: HashMap::default(),
875 failed_corpus_replays: *failed_corpus_replays,
876 }
877 }
878 Self::Table { runs, mean_gas, median_gas } => {
879 TestKindReport::Table { runs: *runs, mean_gas: *mean_gas, median_gas: *median_gas }
880 }
881 }
882 }
883}
884
885#[derive(Clone, Debug, Default)]
890pub struct TestSetup {
891 pub address: Address,
893 pub fuzz_fixtures: FuzzFixtures,
895
896 pub logs: Vec<Log>,
898 pub labels: AddressHashMap<String>,
900 pub traces: Traces,
902 pub coverage: Option<HitMaps>,
904 pub deployed_libs: Vec<Address>,
906
907 pub reason: Option<String>,
909 pub skipped: bool,
911 pub deployment_failure: bool,
913}
914
915impl TestSetup {
916 pub fn failed(reason: String) -> Self {
917 Self { reason: Some(reason), ..Default::default() }
918 }
919
920 pub fn skipped(reason: String) -> Self {
921 Self { reason: Some(reason), skipped: true, ..Default::default() }
922 }
923
924 pub fn extend(&mut self, raw: RawCallResult, trace_kind: TraceKind) {
925 extend!(self, raw, trace_kind);
926 }
927
928 pub fn merge_coverages(&mut self, other_coverage: Option<HitMaps>) {
929 HitMaps::merge_opt(&mut self.coverage, other_coverage);
930 }
931}