Skip to content

Commit

Permalink
feat: add JSON compatibility for forge test --summary +/ --detailed
Browse files Browse the repository at this point in the history
… + apply consistent table styling (#9485)

* support summary reports in json

* unify table style, when show_metrics is enabled and --json, do render

* apply consistent formatting and ordering and spacing for tables

* clean up

* make tables consistent

* update layouts, fix tests

* clean up

* change ReportKind::Markdown to ReportKind::Text as the output is not strictly Markdown compatible

* json compatibility for invariant metrics is not necessary due to different branching and could be derived from JSON

* remove redundant spacer

* clean up, revert InvariantMetricsReporter
  • Loading branch information
zerosnacks authored Dec 5, 2024
1 parent ce9fca2 commit a4de7e8
Showing 17 changed files with 796 additions and 366 deletions.
18 changes: 14 additions & 4 deletions crates/cast/bin/cmd/storage.rs
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ use alloy_rpc_types::BlockId;
use alloy_transport::Transport;
use cast::Cast;
use clap::Parser;
use comfy_table::{presets::ASCII_MARKDOWN, Table};
use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Table};
use eyre::Result;
use foundry_block_explorers::Client;
use foundry_cli::{
@@ -288,8 +288,18 @@ fn print_storage(layout: StorageLayout, values: Vec<StorageValue>, pretty: bool)
}

let mut table = Table::new();
table.load_preset(ASCII_MARKDOWN);
table.set_header(["Name", "Type", "Slot", "Offset", "Bytes", "Value", "Hex Value", "Contract"]);
table.apply_modifier(UTF8_ROUND_CORNERS);

table.set_header(vec![
Cell::new("Name"),
Cell::new("Type"),
Cell::new("Slot"),
Cell::new("Offset"),
Cell::new("Bytes"),
Cell::new("Value"),
Cell::new("Hex Value"),
Cell::new("Contract"),
]);

for (slot, storage_value) in layout.storage.into_iter().zip(values) {
let storage_type = layout.types.get(&slot.storage_type);
@@ -309,7 +319,7 @@ fn print_storage(layout: StorageLayout, values: Vec<StorageValue>, pretty: bool)
]);
}

sh_println!("{table}")?;
sh_println!("\n{table}\n")?;

Ok(())
}
25 changes: 23 additions & 2 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
@@ -1131,10 +1131,15 @@ casttest!(storage_layout_simple, |_prj, cmd| {
])
.assert_success()
.stdout_eq(str![[r#"
╭---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------╮
| Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract |
|---------|---------|------|--------|-------|-------|--------------------------------------------------------------------|-----------------------------------------------|
+========================================================================================================================================================================+
| _owner | address | 0 | 0 | 20 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/Create2Deployer.sol:Create2Deployer |
|---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------|
| _paused | bool | 0 | 20 | 1 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/Create2Deployer.sol:Create2Deployer |
╰---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------╯
"#]]);
});
@@ -1170,21 +1175,37 @@ casttest!(storage_layout_complex, |_prj, cmd| {
])
.assert_success()
.stdout_eq(str![[r#"
╭-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------╮
| Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract |
|-------------------------------|--------------------------------------------------------------------|------|--------|-------|--------------------------------------------------|--------------------------------------------------------------------|---------------------------------|
+======================================================================================================================================================================================================================================================================================+
| _status | uint256 | 0 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _generalPoolsBalances | mapping(bytes32 => struct EnumerableMap.IERC20ToBytes32Map) | 1 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _nextNonce | mapping(address => uint256) | 2 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _paused | bool | 3 | 0 | 1 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _authorizer | contract IAuthorizer | 3 | 1 | 20 | 549683469959765988649777481110995959958745616871 | 0x0000000000000000000000006048a8c631fb7e77eca533cf9c29784e482391e7 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _approvedRelayers | mapping(address => mapping(address => bool)) | 4 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _isPoolRegistered | mapping(bytes32 => bool) | 5 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _nextPoolNonce | uint256 | 6 | 0 | 32 | 1760 | 0x00000000000000000000000000000000000000000000000000000000000006e0 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _minimalSwapInfoPoolsBalances | mapping(bytes32 => mapping(contract IERC20 => bytes32)) | 7 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _minimalSwapInfoPoolsTokens | mapping(bytes32 => struct EnumerableSet.AddressSet) | 8 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _twoTokenPoolTokens | mapping(bytes32 => struct TwoTokenPoolsBalance.TwoTokenPoolTokens) | 9 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _poolAssetManagers | mapping(bytes32 => mapping(contract IERC20 => address)) | 10 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault |
|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------|
| _internalTokenBalance | mapping(address => mapping(contract IERC20 => uint256)) | 11 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault |
╰-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------╯
"#]]);
});
6 changes: 5 additions & 1 deletion crates/common/fmt/src/eof.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use comfy_table::{ContentArrangement, Table};
use comfy_table::{modifiers::UTF8_ROUND_CORNERS, ContentArrangement, Table};
use revm_primitives::{
eof::{EofBody, EofHeader},
Eof,
@@ -24,6 +24,7 @@ pub fn pretty_eof(eof: &Eof) -> Result<String, fmt::Error> {
let mut result = String::new();

let mut table = Table::new();
table.apply_modifier(UTF8_ROUND_CORNERS);
table.add_row(vec!["type_size", &types_size.to_string()]);
table.add_row(vec!["num_code_sections", &code_sizes.len().to_string()]);
if !code_sizes.is_empty() {
@@ -39,6 +40,7 @@ pub fn pretty_eof(eof: &Eof) -> Result<String, fmt::Error> {

if !code_section.is_empty() {
let mut table = Table::new();
table.apply_modifier(UTF8_ROUND_CORNERS);
table.set_content_arrangement(ContentArrangement::Dynamic);
table.set_header(vec!["", "Inputs", "Outputs", "Max stack height", "Code"]);
for (idx, (code, type_section)) in code_section.iter().zip(types_section).enumerate() {
@@ -56,6 +58,7 @@ pub fn pretty_eof(eof: &Eof) -> Result<String, fmt::Error> {

if !container_section.is_empty() {
let mut table = Table::new();
table.apply_modifier(UTF8_ROUND_CORNERS);
table.set_content_arrangement(ContentArrangement::Dynamic);
for (idx, container) in container_section.iter().enumerate() {
table.add_row(vec![&idx.to_string(), &container.to_string()]);
@@ -66,6 +69,7 @@ pub fn pretty_eof(eof: &Eof) -> Result<String, fmt::Error> {

if !data_section.is_empty() {
let mut table = Table::new();
table.apply_modifier(UTF8_ROUND_CORNERS);
table.set_content_arrangement(ContentArrangement::Dynamic);
table.add_row(vec![&data_section.to_string()]);
write!(result, "\n\nData section:\n{table}")?;
40 changes: 16 additions & 24 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ use crate::{
term::SpinnerReporter,
TestFunctionExt,
};
use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, CellAlignment, Color, Table};
use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Table};
use eyre::Result;
use foundry_block_explorers::contract::Metadata;
use foundry_compilers::{
@@ -341,9 +341,8 @@ impl SizeReport {
impl Display for SizeReport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self.report_kind {
ReportKind::Markdown => {
let table = self.format_table_output();
writeln!(f, "{table}")?;
ReportKind::Text => {
writeln!(f, "\n{}", self.format_table_output())?;
}
ReportKind::JSON => {
writeln!(f, "{}", self.format_json_output())?;
@@ -378,13 +377,14 @@ impl SizeReport {

fn format_table_output(&self) -> Table {
let mut table = Table::new();
table.load_preset(ASCII_MARKDOWN);
table.set_header([
Cell::new("Contract").add_attribute(Attribute::Bold).fg(Color::Blue),
Cell::new("Runtime Size (B)").add_attribute(Attribute::Bold).fg(Color::Blue),
Cell::new("Initcode Size (B)").add_attribute(Attribute::Bold).fg(Color::Blue),
Cell::new("Runtime Margin (B)").add_attribute(Attribute::Bold).fg(Color::Blue),
Cell::new("Initcode Margin (B)").add_attribute(Attribute::Bold).fg(Color::Blue),
table.apply_modifier(UTF8_ROUND_CORNERS);

table.set_header(vec![
Cell::new("Contract"),
Cell::new("Runtime Size (B)"),
Cell::new("Initcode Size (B)"),
Cell::new("Runtime Margin (B)"),
Cell::new("Initcode Margin (B)"),
]);

// Filters out dev contracts (Test or Script)
@@ -411,19 +411,11 @@ impl SizeReport {

let locale = &Locale::en;
table.add_row([
Cell::new(name).fg(Color::Blue),
Cell::new(contract.runtime_size.to_formatted_string(locale))
.set_alignment(CellAlignment::Right)
.fg(runtime_color),
Cell::new(contract.init_size.to_formatted_string(locale))
.set_alignment(CellAlignment::Right)
.fg(init_color),
Cell::new(runtime_margin.to_formatted_string(locale))
.set_alignment(CellAlignment::Right)
.fg(runtime_color),
Cell::new(init_margin.to_formatted_string(locale))
.set_alignment(CellAlignment::Right)
.fg(init_color),
Cell::new(name),
Cell::new(contract.runtime_size.to_formatted_string(locale)).fg(runtime_color),
Cell::new(contract.init_size.to_formatted_string(locale)).fg(init_color),
Cell::new(runtime_margin.to_formatted_string(locale)).fg(runtime_color),
Cell::new(init_margin.to_formatted_string(locale)).fg(init_color),
]);
}

4 changes: 2 additions & 2 deletions crates/common/src/reports.rs
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ use crate::shell;
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum ReportKind {
#[default]
Markdown,
Text,
JSON,
}

@@ -14,6 +14,6 @@ pub fn report_kind() -> ReportKind {
if shell::is_json() {
ReportKind::JSON
} else {
ReportKind::Markdown
ReportKind::Text
}
}
6 changes: 3 additions & 3 deletions crates/forge/bin/cmd/coverage.rs
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ use forge::{
coverage::{
analysis::{SourceAnalysis, SourceAnalyzer, SourceFile, SourceFiles},
anchors::find_anchors,
BytecodeReporter, ContractId, CoverageReport, CoverageReporter, DebugReporter, ItemAnchor,
LcovReporter, SummaryReporter,
BytecodeReporter, ContractId, CoverageReport, CoverageReporter, CoverageSummaryReporter,
DebugReporter, ItemAnchor, LcovReporter,
},
opts::EvmOpts,
utils::IcPcMap,
@@ -302,7 +302,7 @@ impl CoverageArgs {
// Output final report
for report_kind in self.report {
match report_kind {
CoverageReportKind::Summary => SummaryReporter::default().report(&report),
CoverageReportKind::Summary => CoverageSummaryReporter::default().report(&report),
CoverageReportKind::Lcov => {
let path =
root.join(self.report_file.as_deref().unwrap_or("lcov.info".as_ref()));
24 changes: 16 additions & 8 deletions crates/forge/bin/cmd/inspect.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use alloy_primitives::{hex, keccak256, Address};
use clap::Parser;
use comfy_table::{presets::ASCII_MARKDOWN, Table};
use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Table};
use eyre::{Context, Result};
use forge::revm::primitives::Eof;
use foundry_cli::opts::{CompilerArgs, CoreBuildArgs};
use foundry_common::{compile::ProjectCompiler, fmt::pretty_eof};
use foundry_common::{compile::ProjectCompiler, fmt::pretty_eof, shell};
use foundry_compilers::{
artifacts::{
output_selection::{
@@ -111,7 +111,7 @@ impl InspectArgs {
print_json(&artifact.gas_estimates)?;
}
ContractArtifactField::StorageLayout => {
print_storage_layout(artifact.storage_layout.as_ref(), pretty)?;
print_storage_layout(artifact.storage_layout.as_ref())?;
}
ContractArtifactField::DevDoc => {
print_json(&artifact.devdoc)?;
@@ -176,18 +176,26 @@ impl InspectArgs {
}
}

pub fn print_storage_layout(storage_layout: Option<&StorageLayout>, pretty: bool) -> Result<()> {
pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<()> {
let Some(storage_layout) = storage_layout else {
eyre::bail!("Could not get storage layout");
};

if !pretty {
if shell::is_json() {
return print_json(&storage_layout)
}

let mut table = Table::new();
table.load_preset(ASCII_MARKDOWN);
table.set_header(["Name", "Type", "Slot", "Offset", "Bytes", "Contract"]);
table.apply_modifier(UTF8_ROUND_CORNERS);

table.set_header(vec![
Cell::new("Name"),
Cell::new("Type"),
Cell::new("Slot"),
Cell::new("Offset"),
Cell::new("Bytes"),
Cell::new("Contract"),
]);

for slot in &storage_layout.storage {
let storage_type = storage_layout.types.get(&slot.storage_type);
@@ -201,7 +209,7 @@ pub fn print_storage_layout(storage_layout: Option<&StorageLayout>, pretty: bool
]);
}

sh_println!("{table}")?;
sh_println!("\n{table}\n")?;
Ok(())
}

Loading

0 comments on commit a4de7e8

Please sign in to comment.