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);
172 }
173
174 sh_println!("\nFailing tests:")?;
175 for (suite_name, suite) in &outcome.results {
176 let failed = suite.failed();
177 if failed == 0 {
178 continue;
179 }
180
181 let term = if failed > 1 { "tests" } else { "test" };
182 sh_println!("Encountered {failed} failing {term} in {suite_name}")?;
183 for (name, result) in suite.failures() {
184 sh_println!("{}", result.short_result(name))?;
185 }
186 sh_println!()?;
187 }
188 let successes = outcome.passed();
189 sh_println!(
190 "Encountered a total of {} failing tests, {} tests succeeded",
191 failures.to_string().red(),
192 successes.to_string().green()
193 )?;
194
195 let test_word = if failures == 1 { "test" } else { "tests" };
197 sh_println!(
198 "\nTip: Run {} to retry only the {} failed {}",
199 "`forge test --rerun`".cyan(),
200 failures,
201 test_word
202 )?;
203
204 std::process::exit(1);
206 }
207
208 pub fn remove_first(&mut self) -> Option<(String, String, TestResult)> {
210 self.results.iter_mut().find_map(|(suite_name, suite)| {
211 if let Some(test_name) = suite.test_results.keys().next().cloned() {
212 let result = suite.test_results.remove(&test_name).unwrap();
213 Some((suite_name.clone(), test_name, result))
214 } else {
215 None
216 }
217 })
218 }
219}
220
221#[derive(Clone, Debug, Serialize)]
223pub struct SuiteResult {
224 #[serde(with = "foundry_common::serde_helpers::duration")]
226 pub duration: Duration,
227 pub test_results: BTreeMap<String, TestResult>,
229 pub warnings: Vec<String>,
231}
232
233impl SuiteResult {
234 pub fn new(
235 duration: Duration,
236 test_results: BTreeMap<String, TestResult>,
237 mut warnings: Vec<String>,
238 ) -> Self {
239 let mut deprecated_cheatcodes = HashMap::new();
241 for test_result in test_results.values() {
242 deprecated_cheatcodes.extend(test_result.deprecated_cheatcodes.clone());
243 }
244 if !deprecated_cheatcodes.is_empty() {
245 let mut warning =
246 "the following cheatcode(s) are deprecated and will be removed in future versions:"
247 .to_string();
248 for (cheatcode, reason) in deprecated_cheatcodes {
249 write!(warning, "\n {cheatcode}").unwrap();
250 if let Some(reason) = reason {
251 write!(warning, ": {reason}").unwrap();
252 }
253 }
254 warnings.push(warning);
255 }
256
257 Self { duration, test_results, warnings }
258 }
259
260 pub fn successes(&self) -> impl Iterator<Item = (&String, &TestResult)> {
262 self.tests().filter(|(_, t)| t.status.is_success())
263 }
264
265 pub fn skips(&self) -> impl Iterator<Item = (&String, &TestResult)> {
267 self.tests().filter(|(_, t)| t.status.is_skipped())
268 }
269
270 pub fn failures(&self) -> impl Iterator<Item = (&String, &TestResult)> {
272 self.tests().filter(|(_, t)| t.status.is_failure())
273 }
274
275 pub fn passed(&self) -> usize {
277 self.successes().count()
278 }
279
280 pub fn skipped(&self) -> usize {
282 self.skips().count()
283 }
284
285 pub fn failed(&self) -> usize {
287 self.failures().count()
288 }
289
290 pub fn tests(&self) -> impl Iterator<Item = (&String, &TestResult)> {
292 self.test_results.iter()
293 }
294
295 pub fn is_empty(&self) -> bool {
297 self.test_results.is_empty()
298 }
299
300 pub fn len(&self) -> usize {
302 self.test_results.len()
303 }
304
305 pub fn total_time(&self) -> Duration {
309 self.test_results.values().map(|result| result.duration).sum()
310 }
311
312 pub fn summary(&self) -> String {
314 let failed = self.failed();
315 let result = if failed == 0 { "ok".green() } else { "FAILED".red() };
316 format!(
317 "Suite result: {}. {} passed; {} failed; {} skipped; finished in {:.2?} ({:.2?} CPU time)",
318 result,
319 self.passed().green(),
320 failed.red(),
321 self.skipped().yellow(),
322 self.duration,
323 self.total_time(),
324 )
325 }
326}
327
328#[derive(Clone, Debug)]
332pub struct SuiteTestResult {
333 pub artifact_id: String,
336 pub signature: String,
338 pub result: TestResult,
340}
341
342impl SuiteTestResult {
343 pub fn gas_used(&self) -> u64 {
345 self.result.kind.report().gas()
346 }
347
348 pub fn contract_name(&self) -> &str {
350 get_contract_name(&self.artifact_id)
351 }
352
353 pub fn file_name(&self) -> &str {
355 get_file_name(&self.artifact_id)
356 }
357}
358
359#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
361pub enum TestStatus {
362 Success,
363 #[default]
364 Failure,
365 Skipped,
366}
367
368impl TestStatus {
369 #[inline]
371 pub fn is_success(self) -> bool {
372 matches!(self, Self::Success)
373 }
374
375 #[inline]
377 pub fn is_failure(self) -> bool {
378 matches!(self, Self::Failure)
379 }
380
381 #[inline]
383 pub fn is_skipped(self) -> bool {
384 matches!(self, Self::Skipped)
385 }
386}
387
388#[derive(Clone, Debug, Default, Serialize, Deserialize)]
390pub struct TestResult {
391 pub status: TestStatus,
396
397 pub reason: Option<String>,
400
401 pub counterexample: Option<CounterExample>,
403
404 pub logs: Vec<Log>,
407
408 pub decoded_logs: Vec<String>,
411
412 pub kind: TestKind,
414
415 pub traces: Traces,
417
418 #[serde(skip)]
422 pub gas_report_traces: Vec<Vec<CallTraceArena>>,
423
424 #[serde(skip)]
426 pub line_coverage: Option<HitMaps>,
427
428 #[serde(rename = "labeled_addresses")] pub labels: AddressHashMap<String>,
431
432 #[serde(with = "foundry_common::serde_helpers::duration")]
433 pub duration: Duration,
434
435 pub breakpoints: Breakpoints,
437
438 pub gas_snapshots: BTreeMap<String, BTreeMap<String, String>>,
440
441 #[serde(skip)]
443 pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>,
444}
445
446impl fmt::Display for TestResult {
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 match self.status {
449 TestStatus::Success => "[PASS]".green().fmt(f),
450 TestStatus::Skipped => {
451 let mut s = String::from("[SKIP");
452 if let Some(reason) = &self.reason {
453 write!(s, ": {reason}").unwrap();
454 }
455 s.push(']');
456 s.yellow().fmt(f)
457 }
458 TestStatus::Failure => {
459 let mut s = String::from("[FAIL");
460 if self.reason.is_some() || self.counterexample.is_some() {
461 if let Some(reason) = &self.reason {
462 write!(s, ": {reason}").unwrap();
463 }
464
465 if let Some(counterexample) = &self.counterexample {
466 match counterexample {
467 CounterExample::Single(ex) => {
468 write!(s, "; counterexample: {ex}]").unwrap();
469 }
470 CounterExample::Sequence(original, sequence) => {
471 s.push_str(
472 format!(
473 "]\n\t[Sequence] (original: {original}, shrunk: {})\n",
474 sequence.len()
475 )
476 .as_str(),
477 );
478 for ex in sequence {
479 writeln!(s, "{ex}").unwrap();
480 }
481 }
482 }
483 } else {
484 s.push(']');
485 }
486 } else {
487 s.push(']');
488 }
489 s.red().wrap().fmt(f)
490 }
491 }
492 }
493}
494
495macro_rules! extend {
496 ($a:expr, $b:expr, $trace_kind:expr) => {
497 $a.logs.extend($b.logs);
498 $a.labels.extend($b.labels);
499 $a.traces.extend($b.traces.map(|traces| ($trace_kind, traces)));
500 $a.merge_coverages($b.line_coverage);
501 };
502}
503
504impl TestResult {
505 pub fn new(setup: &TestSetup) -> Self {
507 Self {
508 labels: setup.labels.clone(),
509 logs: setup.logs.clone(),
510 traces: setup.traces.clone(),
511 line_coverage: setup.coverage.clone(),
512 ..Default::default()
513 }
514 }
515
516 pub fn fail(reason: String) -> Self {
518 Self { status: TestStatus::Failure, reason: Some(reason), ..Default::default() }
519 }
520
521 pub fn setup_result(setup: TestSetup) -> Self {
523 let TestSetup {
524 address: _,
525 fuzz_fixtures: _,
526 logs,
527 labels,
528 traces,
529 coverage,
530 deployed_libs: _,
531 reason,
532 skipped,
533 deployment_failure: _,
534 } = setup;
535 Self {
536 status: if skipped { TestStatus::Skipped } else { TestStatus::Failure },
537 reason,
538 logs,
539 traces,
540 line_coverage: coverage,
541 labels,
542 ..Default::default()
543 }
544 }
545
546 pub fn single_skip(&mut self, reason: SkipReason) {
548 self.status = TestStatus::Skipped;
549 self.reason = reason.0;
550 }
551
552 pub fn single_fail(&mut self, reason: Option<String>) {
554 self.status = TestStatus::Failure;
555 self.reason = reason;
556 }
557
558 pub fn single_result(
561 &mut self,
562 success: bool,
563 reason: Option<String>,
564 raw_call_result: RawCallResult,
565 ) {
566 self.kind =
567 TestKind::Unit { gas: raw_call_result.gas_used.wrapping_sub(raw_call_result.stipend) };
568
569 extend!(self, raw_call_result, TraceKind::Execution);
570
571 self.status = match success {
572 true => TestStatus::Success,
573 false => TestStatus::Failure,
574 };
575 self.reason = reason;
576 self.duration = Duration::default();
577 self.gas_report_traces = Vec::new();
578
579 if let Some(cheatcodes) = raw_call_result.cheatcodes {
580 self.breakpoints = cheatcodes.breakpoints;
581 self.gas_snapshots = cheatcodes.gas_snapshots;
582 self.deprecated_cheatcodes = cheatcodes.deprecated;
583 }
584 }
585
586 pub fn fuzz_result(&mut self, result: FuzzTestResult) {
589 self.kind = TestKind::Fuzz {
590 median_gas: result.median_gas(false),
591 mean_gas: result.mean_gas(false),
592 first_case: result.first_case,
593 runs: result.gas_by_case.len(),
594 failed_corpus_replays: result.failed_corpus_replays,
595 };
596
597 extend!(self, result, TraceKind::Execution);
599
600 self.status = if result.skipped {
601 TestStatus::Skipped
602 } else if result.success {
603 TestStatus::Success
604 } else {
605 TestStatus::Failure
606 };
607 self.reason = result.reason;
608 self.counterexample = result.counterexample;
609 self.duration = Duration::default();
610 self.gas_report_traces = result.gas_report_traces.into_iter().map(|t| vec![t]).collect();
611 self.breakpoints = result.breakpoints.unwrap_or_default();
612 self.deprecated_cheatcodes = result.deprecated_cheatcodes;
613 }
614
615 pub fn fuzz_setup_fail(&mut self, e: Report) {
617 self.kind = TestKind::Fuzz {
618 first_case: Default::default(),
619 runs: 0,
620 mean_gas: 0,
621 median_gas: 0,
622 failed_corpus_replays: 0,
623 };
624 self.status = TestStatus::Failure;
625 self.reason = Some(format!("failed to set up fuzz testing environment: {e}"));
626 }
627
628 pub fn invariant_skip(&mut self, reason: SkipReason) {
630 self.kind = TestKind::Invariant {
631 runs: 1,
632 calls: 1,
633 reverts: 1,
634 metrics: HashMap::default(),
635 failed_corpus_replays: 0,
636 };
637 self.status = TestStatus::Skipped;
638 self.reason = reason.0;
639 }
640
641 pub fn invariant_replay_fail(
643 &mut self,
644 replayed_entirely: bool,
645 invariant_name: &String,
646 call_sequence: Vec<BaseCounterExample>,
647 ) {
648 self.kind = TestKind::Invariant {
649 runs: 1,
650 calls: 1,
651 reverts: 1,
652 metrics: HashMap::default(),
653 failed_corpus_replays: 0,
654 };
655 self.status = TestStatus::Failure;
656 self.reason = if replayed_entirely {
657 Some(format!("{invariant_name} replay failure"))
658 } else {
659 Some(format!("{invariant_name} persisted failure revert"))
660 };
661 self.counterexample = Some(CounterExample::Sequence(call_sequence.len(), call_sequence));
662 }
663
664 pub fn invariant_setup_fail(&mut self, e: Report) {
666 self.kind = TestKind::Invariant {
667 runs: 0,
668 calls: 0,
669 reverts: 0,
670 metrics: HashMap::default(),
671 failed_corpus_replays: 0,
672 };
673 self.status = TestStatus::Failure;
674 self.reason = Some(format!("failed to set up invariant testing environment: {e}"));
675 }
676
677 #[expect(clippy::too_many_arguments)]
679 pub fn invariant_result(
680 &mut self,
681 gas_report_traces: Vec<Vec<CallTraceArena>>,
682 success: bool,
683 reason: Option<String>,
684 counterexample: Option<CounterExample>,
685 cases: Vec<FuzzedCases>,
686 reverts: usize,
687 metrics: Map<String, InvariantMetrics>,
688 failed_corpus_replays: usize,
689 ) {
690 self.kind = TestKind::Invariant {
691 runs: cases.len(),
692 calls: cases.iter().map(|sequence| sequence.cases().len()).sum(),
693 reverts,
694 metrics,
695 failed_corpus_replays,
696 };
697 self.status = match success {
698 true => TestStatus::Success,
699 false => TestStatus::Failure,
700 };
701 self.reason = reason;
702 self.counterexample = counterexample;
703 self.gas_report_traces = gas_report_traces;
704 }
705
706 pub fn table_result(&mut self, result: FuzzTestResult) {
709 self.kind = TestKind::Table {
710 median_gas: result.median_gas(false),
711 mean_gas: result.mean_gas(false),
712 runs: result.gas_by_case.len(),
713 };
714
715 extend!(self, result, TraceKind::Execution);
717
718 self.status = if result.skipped {
719 TestStatus::Skipped
720 } else if result.success {
721 TestStatus::Success
722 } else {
723 TestStatus::Failure
724 };
725 self.reason = result.reason;
726 self.counterexample = result.counterexample;
727 self.duration = Duration::default();
728 self.gas_report_traces = result.gas_report_traces.into_iter().map(|t| vec![t]).collect();
729 self.breakpoints = result.breakpoints.unwrap_or_default();
730 self.deprecated_cheatcodes = result.deprecated_cheatcodes;
731 }
732
733 pub fn is_fuzz(&self) -> bool {
735 matches!(self.kind, TestKind::Fuzz { .. })
736 }
737
738 pub fn short_result(&self, name: &str) -> String {
740 format!("{self} {name} {}", self.kind.report())
741 }
742
743 pub fn extend(&mut self, call_result: RawCallResult) {
745 extend!(self, call_result, TraceKind::Execution);
746 }
747
748 pub fn merge_coverages(&mut self, other_coverage: Option<HitMaps>) {
750 HitMaps::merge_opt(&mut self.line_coverage, other_coverage);
751 }
752}
753
754#[derive(Clone, Debug, PartialEq, Eq)]
756pub enum TestKindReport {
757 Unit {
758 gas: u64,
759 },
760 Fuzz {
761 runs: usize,
762 mean_gas: u64,
763 median_gas: u64,
764 failed_corpus_replays: usize,
765 },
766 Invariant {
767 runs: usize,
768 calls: usize,
769 reverts: usize,
770 metrics: Map<String, InvariantMetrics>,
771 failed_corpus_replays: usize,
772 },
773 Table {
774 runs: usize,
775 mean_gas: u64,
776 median_gas: u64,
777 },
778}
779
780impl fmt::Display for TestKindReport {
781 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
782 match self {
783 Self::Unit { gas } => {
784 write!(f, "(gas: {gas})")
785 }
786 Self::Fuzz { runs, mean_gas, median_gas, failed_corpus_replays } => {
787 if *failed_corpus_replays != 0 {
788 write!(
789 f,
790 "(runs: {runs}, μ: {mean_gas}, ~: {median_gas}, failed corpus replays: {failed_corpus_replays})"
791 )
792 } else {
793 write!(f, "(runs: {runs}, μ: {mean_gas}, ~: {median_gas})")
794 }
795 }
796 Self::Invariant { runs, calls, reverts, metrics: _, failed_corpus_replays } => {
797 if *failed_corpus_replays != 0 {
798 write!(
799 f,
800 "(runs: {runs}, calls: {calls}, reverts: {reverts}, failed corpus replays: {failed_corpus_replays})"
801 )
802 } else {
803 write!(f, "(runs: {runs}, calls: {calls}, reverts: {reverts})")
804 }
805 }
806 Self::Table { runs, mean_gas, median_gas } => {
807 write!(f, "(runs: {runs}, μ: {mean_gas}, ~: {median_gas})")
808 }
809 }
810 }
811}
812
813impl TestKindReport {
814 pub fn gas(&self) -> u64 {
816 match *self {
817 Self::Unit { gas } => gas,
818 Self::Fuzz { median_gas, .. } | Self::Table { median_gas, .. } => median_gas,
820 Self::Invariant { .. } => 0,
822 }
823 }
824}
825
826#[derive(Clone, Debug, Serialize, Deserialize)]
828pub enum TestKind {
829 Unit { gas: u64 },
831 Fuzz {
833 first_case: FuzzCase,
835 runs: usize,
836 mean_gas: u64,
837 median_gas: u64,
838 failed_corpus_replays: usize,
839 },
840 Invariant {
842 runs: usize,
843 calls: usize,
844 reverts: usize,
845 metrics: Map<String, InvariantMetrics>,
846 failed_corpus_replays: usize,
847 },
848 Table { runs: usize, mean_gas: u64, median_gas: u64 },
850}
851
852impl Default for TestKind {
853 fn default() -> Self {
854 Self::Unit { gas: 0 }
855 }
856}
857
858impl TestKind {
859 pub fn report(&self) -> TestKindReport {
861 match self {
862 Self::Unit { gas } => TestKindReport::Unit { gas: *gas },
863 Self::Fuzz { first_case: _, runs, mean_gas, median_gas, failed_corpus_replays } => {
864 TestKindReport::Fuzz {
865 runs: *runs,
866 mean_gas: *mean_gas,
867 median_gas: *median_gas,
868 failed_corpus_replays: *failed_corpus_replays,
869 }
870 }
871 Self::Invariant { runs, calls, reverts, metrics: _, failed_corpus_replays } => {
872 TestKindReport::Invariant {
873 runs: *runs,
874 calls: *calls,
875 reverts: *reverts,
876 metrics: HashMap::default(),
877 failed_corpus_replays: *failed_corpus_replays,
878 }
879 }
880 Self::Table { runs, mean_gas, median_gas } => {
881 TestKindReport::Table { runs: *runs, mean_gas: *mean_gas, median_gas: *median_gas }
882 }
883 }
884 }
885}
886
887#[derive(Clone, Debug, Default)]
892pub struct TestSetup {
893 pub address: Address,
895 pub fuzz_fixtures: FuzzFixtures,
897
898 pub logs: Vec<Log>,
900 pub labels: AddressHashMap<String>,
902 pub traces: Traces,
904 pub coverage: Option<HitMaps>,
906 pub deployed_libs: Vec<Address>,
908
909 pub reason: Option<String>,
911 pub skipped: bool,
913 pub deployment_failure: bool,
915}
916
917impl TestSetup {
918 pub fn failed(reason: String) -> Self {
919 Self { reason: Some(reason), ..Default::default() }
920 }
921
922 pub fn skipped(reason: String) -> Self {
923 Self { reason: Some(reason), skipped: true, ..Default::default() }
924 }
925
926 pub fn extend(&mut self, raw: RawCallResult, trace_kind: TraceKind) {
927 extend!(self, raw, trace_kind);
928 }
929
930 pub fn merge_coverages(&mut self, other_coverage: Option<HitMaps>) {
931 HitMaps::merge_opt(&mut self.coverage, other_coverage);
932 }
933}