diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 6c854150a4017..019e44e8c3255 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -56,7 +56,7 @@ mod summary; pub use filter::FilterArgs; use forge::{result::TestKind, traces::render_trace_arena_inner}; use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite}; -use summary::{print_invariant_metrics, TestSummaryReport}; +use summary::{format_invariant_metrics_table, TestSummaryReport}; // Loads project's figment and merges the build cli arguments into it foundry_config::merge_impl_figment_convert!(TestArgs, build, evm); @@ -569,7 +569,9 @@ impl TestArgs { // Display invariant metrics if invariant kind. if let TestKind::Invariant { metrics, .. } = &result.kind { - print_invariant_metrics(metrics); + if !metrics.is_empty() { + let _ = sh_println!("\n{}\n", format_invariant_metrics_table(metrics)); + } } // We only display logs at level 2 and above diff --git a/crates/forge/bin/cmd/test/summary.rs b/crates/forge/bin/cmd/test/summary.rs index eabf7bd9eaaa3..68ab3f4590b05 100644 --- a/crates/forge/bin/cmd/test/summary.rs +++ b/crates/forge/bin/cmd/test/summary.rs @@ -124,7 +124,7 @@ impl TestSummaryReport { } } -/// Helper function to print the invariant metrics. +/// Helper function to create the invariant metrics table. /// /// ╭-----------------------+----------------+-------+---------+----------╮ /// | Contract | Selector | Calls | Reverts | Discards | @@ -137,55 +137,90 @@ impl TestSummaryReport { /// |-----------------------+----------------+-------+---------+----------| /// | CounterHandler | doSomething | 7382 | 160 |4794 | /// ╰-----------------------+----------------+-------+---------+----------╯ -pub(crate) fn print_invariant_metrics(test_metrics: &HashMap) { - if !test_metrics.is_empty() { - let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); +pub(crate) fn format_invariant_metrics_table( + test_metrics: &HashMap, +) -> Table { + let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![ + Cell::new("Contract"), + Cell::new("Selector"), + Cell::new("Calls").fg(Color::Green), + Cell::new("Reverts").fg(Color::Red), + Cell::new("Discards").fg(Color::Yellow), + ]); + + for name in test_metrics.keys().sorted() { + if let Some((contract, selector)) = + name.split_once(':').map_or(name.as_str(), |(_, contract)| contract).split_once('.') + { + let mut row = Row::new(); + row.add_cell(Cell::new(contract)); + row.add_cell(Cell::new(selector)); + + if let Some(metrics) = test_metrics.get(name) { + let calls_cell = Cell::new(metrics.calls).fg(if metrics.calls > 0 { + Color::Green + } else { + Color::White + }); - table.set_header(vec![ - Cell::new("Contract"), - Cell::new("Selector"), - Cell::new("Calls").fg(Color::Green), - Cell::new("Reverts").fg(Color::Red), - Cell::new("Discards").fg(Color::Yellow), - ]); + let reverts_cell = Cell::new(metrics.reverts).fg(if metrics.reverts > 0 { + Color::Red + } else { + Color::White + }); - for name in test_metrics.keys().sorted() { - if let Some((contract, selector)) = - name.split_once(':').and_then(|(_, contract)| contract.split_once('.')) - { - let mut row = Row::new(); - row.add_cell(Cell::new(contract)); - row.add_cell(Cell::new(selector)); - - if let Some(metrics) = test_metrics.get(name) { - let calls_cell = Cell::new(metrics.calls).fg(if metrics.calls > 0 { - Color::Green - } else { - Color::White - }); - - let reverts_cell = Cell::new(metrics.reverts).fg(if metrics.reverts > 0 { - Color::Red - } else { - Color::White - }); - - let discards_cell = Cell::new(metrics.discards).fg(if metrics.discards > 0 { - Color::Yellow - } else { - Color::White - }); - - row.add_cell(calls_cell); - row.add_cell(reverts_cell); - row.add_cell(discards_cell); - } + let discards_cell = Cell::new(metrics.discards).fg(if metrics.discards > 0 { + Color::Yellow + } else { + Color::White + }); - table.add_row(row); + row.add_cell(calls_cell); + row.add_cell(reverts_cell); + row.add_cell(discards_cell); } + + table.add_row(row); } + } + table +} - let _ = sh_println!("\n{table}\n"); +#[cfg(test)] +mod tests { + use crate::cmd::test::summary::format_invariant_metrics_table; + use foundry_evm::executors::invariant::InvariantMetrics; + use std::collections::HashMap; + + #[test] + fn test_invariant_metrics_table() { + let mut test_metrics = HashMap::new(); + test_metrics.insert( + "SystemConfig.setGasLimit".to_string(), + InvariantMetrics { calls: 10, reverts: 1, discards: 1 }, + ); + test_metrics.insert( + "src/universal/Proxy.sol:Proxy.changeAdmin".to_string(), + InvariantMetrics { calls: 20, reverts: 2, discards: 2 }, + ); + let table = format_invariant_metrics_table(&test_metrics); + assert_eq!(table.row_count(), 2); + + let mut first_row_content = table.row(0).unwrap().cell_iter(); + assert_eq!(first_row_content.next().unwrap().content(), "SystemConfig"); + assert_eq!(first_row_content.next().unwrap().content(), "setGasLimit"); + assert_eq!(first_row_content.next().unwrap().content(), "10"); + assert_eq!(first_row_content.next().unwrap().content(), "1"); + assert_eq!(first_row_content.next().unwrap().content(), "1"); + + let mut second_row_content = table.row(1).unwrap().cell_iter(); + assert_eq!(second_row_content.next().unwrap().content(), "Proxy"); + assert_eq!(second_row_content.next().unwrap().content(), "changeAdmin"); + assert_eq!(second_row_content.next().unwrap().content(), "20"); + assert_eq!(second_row_content.next().unwrap().content(), "2"); + assert_eq!(second_row_content.next().unwrap().content(), "2"); } }