huff_neo_test_runner/
report.rs1use crate::prelude::{HuffFunction, Outcome, ReportKind, TestResult, TestStatus, decode_huff_internal_calls};
2use alloy_primitives::Address;
3use comfy_table::{Attribute, Cell, Color, ContentArrangement, Row, Table, modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL};
4use foundry_evm::decode::decode_console_logs;
5use foundry_evm::traces::{
6 CallTraceDecoderBuilder, SparsedTraceArena, decode_trace_arena, identifier::SignaturesIdentifier, render_trace_arena_inner,
7};
8use std::time::Instant;
9use yansi::Paint;
10
11pub async fn print_test_report(results: Vec<TestResult>, report_kind: ReportKind, verbosity: u8, start: Instant) -> eyre::Result<()> {
16 let n_passed = results.iter().filter(|r| r.status == TestStatus::Success).count();
20 let n_skipped = results.iter().filter(|r| r.status == TestStatus::Skipped).count();
21 let n_failed = results.len() - n_passed - n_skipped;
22
23 match report_kind {
26 ReportKind::Table => {
27 let mut table = Table::new();
28 table.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS);
29 table.set_header(Row::from(vec![
30 Cell::new("Name").fg(Color::Magenta),
31 Cell::new("Return Data").fg(Color::Yellow),
32 Cell::new("Gas").fg(Color::Cyan),
33 Cell::new("Status").fg(Color::Blue),
34 ]));
35 table.set_content_arrangement(ContentArrangement::DynamicFullWidth);
36 table.set_width(120);
37
38 for result in results {
39 table.add_row(Row::from(vec![
40 Cell::new(result.name).add_attribute(Attribute::Bold).fg(Color::Cyan),
41 Cell::new(result.return_data.unwrap_or_else(|| String::from("None"))),
42 Cell::new(result.gas.to_string()),
43 Cell::from(result.status),
44 ]));
45 }
46
47 println!("{table}");
48 }
49 ReportKind::List => {
50 for result in results {
51 println!(
52 "[{0}] {1: <15} - {2} {3: <20}",
53 String::from(result.status.clone()),
54 result.name,
55 Paint::yellow("Gas used:"),
56 result.gas
57 );
58 if let Outcome::Skip { reason: Some(reason) } = &result.outcome {
59 println!("{}", Paint::yellow(&format!("├─ skipped: {reason}")));
60 }
61 for failure in &result.failures {
62 println!("{}", Paint::red(&format!("├─ {}: {}", failure.expectation, failure.message)));
63 }
64
65 let num_logs = result.logs.as_ref().map_or(0, Vec::len);
66
67 if let Some(return_data) = result.return_data.clone() {
68 println!("├─ {}", Paint::cyan("RETURN DATA"));
69 println!("{} {return_data}", if num_logs == 0 { "└─" } else { "├─" });
70 }
71
72 if let Some(logs) = &result.logs {
73 let filtered_logs = decode_console_logs(logs);
74 if !filtered_logs.is_empty() {
75 println!("├─ {}", Paint::cyan("CONSOLE LOGS"));
76 for (i, log) in filtered_logs.iter().enumerate() {
77 println!("{} {log}", if filtered_logs.len() - 1 == i { "└─" } else { "├─" });
78 }
79 }
80 }
81
82 let show_traces = verbosity >= 4 || (verbosity == 3 && result.status == TestStatus::Revert);
83 if show_traces && let Some(traces) = result.traces {
84 print_call_trace(traces, result.address, &result.functions, verbosity >= 5).await?;
85 }
86 }
87 }
88 ReportKind::Json => {
89 if let Ok(o) = serde_json::to_string_pretty(&results) {
90 println!("{o}");
91 } else {
92 eprintln!("Error serializing test results into JSON.");
93 }
94 return Ok(());
95 }
96 }
97 let skipped = if n_skipped > 0 { format!(", {} skipped", Paint::yellow(&n_skipped)) } else { String::new() };
98 let share = (n_passed * 100).checked_div(n_passed + n_failed).map(|share| format!(" ({}%)", Paint::yellow(&share))).unwrap_or_default();
100 println!(
101 "➜ {} tests passed, {} tests failed{skipped}{share}. ⏱ : {}",
102 Paint::green(&n_passed),
103 Paint::red(&n_failed),
104 Paint::magenta(&format!("{:.4?}", start.elapsed()))
105 );
106
107 Ok(())
108}
109
110async fn print_call_trace(
112 mut arena: SparsedTraceArena,
113 address: Option<Address>,
114 functions: &[HuffFunction],
115 with_storage_changes: bool,
116) -> eyre::Result<()> {
117 let identifier = SignaturesIdentifier::new(false)?;
119
120 let decoder = CallTraceDecoderBuilder::new().with_signature_identifier(identifier).build();
122
123 decode_trace_arena(&mut arena, &decoder).await;
125 if let Some(address) = address {
126 decode_huff_internal_calls(&mut arena, address, functions);
127 }
128
129 println!("├─ {}", Paint::cyan("TRACES"));
130 println!("{}", render_trace_arena_inner(&arena, false, with_storage_changes));
131
132 Ok(())
133}