Skip to content

Commit cba6e97

Browse files
authored
chore: deprecate --debug regex argument (#8930)
* chore: deprecate --debug regex argument * fix: enable full internal decoding if exactly one test matched
1 parent dab9036 commit cba6e97

File tree

3 files changed

+52
-72
lines changed

3 files changed

+52
-72
lines changed

crates/evm/traces/src/folded_stack_trace.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,13 @@ impl EvmFoldedStackTraceBuilder {
3030
let node = &nodes[idx];
3131

3232
let func_name = if node.trace.kind.is_any_create() {
33-
let default_contract_name = "Contract".to_string();
34-
let contract_name = node.trace.decoded.label.as_ref().unwrap_or(&default_contract_name);
33+
let contract_name = node.trace.decoded.label.as_deref().unwrap_or("Contract");
3534
format!("new {contract_name}")
3635
} else {
3736
let selector = node
3837
.selector()
3938
.map(|selector| selector.encode_hex_with_prefix())
40-
.unwrap_or("fallback".to_string());
39+
.unwrap_or_else(|| "fallback".to_string());
4140
let signature =
4241
node.trace.decoded.call_data.as_ref().map(|dc| &dc.signature).unwrap_or(&selector);
4342

@@ -114,9 +113,11 @@ impl EvmFoldedStackTraceBuilder {
114113
/// Helps to translate a function enter-exit flow into a folded stack trace.
115114
///
116115
/// Example:
117-
/// fn top() { child_a(); child_b() } // consumes 500 gas
118-
/// fn child_a() {} // consumes 100 gas
119-
/// fn child_b() {} // consumes 200 gas
116+
/// ```solidity
117+
/// function top() { child_a(); child_b() } // consumes 500 gas
118+
/// function child_a() {} // consumes 100 gas
119+
/// function child_b() {} // consumes 200 gas
120+
/// ```
120121
///
121122
/// For execution of the `top` function looks like:
122123
/// 1. enter `top`

crates/forge/bin/cmd/test/mod.rs

Lines changed: 37 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use foundry_cli::{
2020
opts::CoreBuildArgs,
2121
utils::{self, LoadConfig},
2222
};
23-
use foundry_common::{compile::ProjectCompiler, evm::EvmArgs, fs, shell};
23+
use foundry_common::{cli_warn, compile::ProjectCompiler, evm::EvmArgs, fs, shell};
2424
use foundry_compilers::{
2525
artifacts::output_selection::OutputSelection,
2626
compilers::{multi::MultiCompilerLanguage, CompilerSettings, Language},
@@ -67,29 +67,20 @@ pub struct TestArgs {
6767
#[arg(value_hint = ValueHint::FilePath)]
6868
pub path: Option<GlobMatcher>,
6969

70-
/// Run a test in the debugger.
71-
///
72-
/// The argument passed to this flag is the **regex** of the test function signature you want
73-
/// to run, and it works the same as --match-test.
74-
///
75-
/// If more than one test matches your specified criteria, you must add additional filters
76-
/// until only one test is found (see --match-contract and --match-path).
70+
/// Run a single test in the debugger.
7771
///
7872
/// The matching test will be opened in the debugger regardless of the outcome of the test.
7973
///
8074
/// If the matching test is a fuzz test, then it will open the debugger on the first failure
81-
/// case.
82-
/// If the fuzz test does not fail, it will open the debugger on the last fuzz case.
83-
///
84-
/// For more fine-grained control of which fuzz case is run, see forge run.
85-
#[arg(long, value_name = "TEST_FUNCTION")]
86-
debug: Option<Regex>,
75+
/// case. If the fuzz test does not fail, it will open the debugger on the last fuzz case.
76+
#[arg(long, value_name = "DEPRECATED_TEST_FUNCTION_REGEX")]
77+
debug: Option<Option<Regex>>,
8778

8879
/// Generate a flamegraph for a single test. Implies `--decode-internal`.
8980
///
9081
/// A flame graph is used to visualize which functions or operations within the smart contract
9182
/// are consuming the most gas overall in a sorted manner.
92-
#[arg(long, conflicts_with = "flamechart")]
83+
#[arg(long)]
9384
flamegraph: bool,
9485

9586
/// Generate a flamechart for a single test. Implies `--decode-internal`.
@@ -99,18 +90,13 @@ pub struct TestArgs {
9990
#[arg(long, conflicts_with = "flamegraph")]
10091
flamechart: bool,
10192

102-
/// Whether to identify internal functions in traces.
93+
/// Identify internal functions in traces.
10394
///
104-
/// If no argument is passed to this flag, it will trace internal functions scope and decode
105-
/// stack parameters, but parameters stored in memory (such as bytes or arrays) will not be
106-
/// decoded.
95+
/// This will trace internal functions and decode stack parameters.
10796
///
108-
/// To decode memory parameters, you should pass an argument with a test function name,
109-
/// similarly to --debug and --match-test.
110-
///
111-
/// If more than one test matches your specified criteria, you must add additional filters
112-
/// until only one test is found (see --match-contract and --match-path).
113-
#[arg(long, value_name = "TEST_FUNCTION")]
97+
/// Parameters stored in memory (such as bytes or arrays) are currently decoded only when a
98+
/// single function is matched, similarly to `--debug`, for performance reasons.
99+
#[arg(long, value_name = "DEPRECATED_TEST_FUNCTION_REGEX")]
114100
decode_internal: Option<Option<Regex>>,
115101

116102
/// Print a gas report.
@@ -342,19 +328,15 @@ impl TestArgs {
342328
let env = evm_opts.evm_env().await?;
343329

344330
// Enable internal tracing for more informative flamegraph.
345-
if should_draw {
331+
if should_draw && self.decode_internal.is_none() {
346332
self.decode_internal = Some(None);
347333
}
348334

349335
// Choose the internal function tracing mode, if --decode-internal is provided.
350-
let decode_internal = if let Some(maybe_fn) = self.decode_internal.as_ref() {
351-
if maybe_fn.is_some() {
352-
// If function filter is provided, we enable full tracing.
353-
InternalTraceMode::Full
354-
} else {
355-
// If no function filter is provided, we enable simple tracing.
356-
InternalTraceMode::Simple
357-
}
336+
let decode_internal = if self.decode_internal.is_some() {
337+
// If more than one function matched, we enable simple tracing.
338+
// If only one function matched, we enable full tracing. This is done in `run_tests`.
339+
InternalTraceMode::Simple
358340
} else {
359341
InternalTraceMode::None
360342
};
@@ -373,26 +355,27 @@ impl TestArgs {
373355
.alphanet(evm_opts.alphanet)
374356
.build(project_root, &output, env, evm_opts)?;
375357

376-
let mut maybe_override_mt = |flag, maybe_regex: Option<&Regex>| {
377-
if let Some(regex) = maybe_regex {
358+
let mut maybe_override_mt = |flag, maybe_regex: Option<&Option<Regex>>| {
359+
if let Some(Some(regex)) = maybe_regex {
360+
cli_warn!(
361+
"specifying argument for --{flag} is deprecated and will be removed in the future, \
362+
use --match-test instead"
363+
);
364+
378365
let test_pattern = &mut filter.args_mut().test_pattern;
379366
if test_pattern.is_some() {
380367
eyre::bail!(
381368
"Cannot specify both --{flag} and --match-test. \
382-
Use --match-contract and --match-path to further limit the search instead."
369+
Use --match-contract and --match-path to further limit the search instead."
383370
);
384371
}
385372
*test_pattern = Some(regex.clone());
386373
}
387374

388375
Ok(())
389376
};
390-
391377
maybe_override_mt("debug", self.debug.as_ref())?;
392-
maybe_override_mt(
393-
"decode-internal",
394-
self.decode_internal.as_ref().and_then(|v| v.as_ref()),
395-
)?;
378+
maybe_override_mt("decode-internal", self.decode_internal.as_ref())?;
396379

397380
let libraries = runner.libraries.clone();
398381
let mut outcome = self.run_tests(runner, config, verbosity, &filter, &output).await?;
@@ -401,18 +384,10 @@ impl TestArgs {
401384
let (suite_name, test_name, mut test_result) =
402385
outcome.remove_first().ok_or_eyre("no tests were executed")?;
403386

404-
let arena = test_result
387+
let (_, arena) = test_result
405388
.traces
406389
.iter_mut()
407-
.find_map(
408-
|(kind, arena)| {
409-
if *kind == TraceKind::Execution {
410-
Some(arena)
411-
} else {
412-
None
413-
}
414-
},
415-
)
390+
.find(|(kind, _)| *kind == TraceKind::Execution)
416391
.unwrap();
417392

418393
// Decode traces.
@@ -425,6 +400,7 @@ impl TestArgs {
425400
let test_name = test_name.trim_end_matches("()");
426401
let file_name = format!("cache/{label}_{contract}_{test_name}.svg");
427402
let file = std::fs::File::create(&file_name).wrap_err("failed to create file")?;
403+
let file = std::io::BufWriter::new(file);
428404

429405
let mut options = inferno::flamegraph::Options::default();
430406
options.title = format!("{label} {contract}::{test_name}");
@@ -435,13 +411,13 @@ impl TestArgs {
435411
}
436412

437413
// Generate SVG.
438-
inferno::flamegraph::from_lines(&mut options, fst.iter().map(|s| s.as_str()), file)
414+
inferno::flamegraph::from_lines(&mut options, fst.iter().map(String::as_str), file)
439415
.wrap_err("failed to write svg")?;
440416
println!("\nSaved to {file_name}");
441417

442418
// Open SVG in default program.
443-
if opener::open(&file_name).is_err() {
444-
println!("\nFailed to open {file_name}. Please open it manually.");
419+
if let Err(e) = opener::open(&file_name) {
420+
eprintln!("\nFailed to open {file_name}; please open it manually: {e}");
445421
}
446422
}
447423

@@ -488,12 +464,7 @@ impl TestArgs {
488464
trace!(target: "forge::test", "running all tests");
489465

490466
let num_filtered = runner.matching_test_functions(filter).count();
491-
if (self.debug.is_some() ||
492-
self.decode_internal.as_ref().map_or(false, |v| v.is_some()) ||
493-
self.flamegraph ||
494-
self.flamechart) &&
495-
num_filtered != 1
496-
{
467+
if num_filtered != 1 && (self.debug.is_some() || self.flamegraph || self.flamechart) {
497468
let action = if self.flamegraph {
498469
"generate a flamegraph"
499470
} else if self.flamechart {
@@ -512,6 +483,11 @@ impl TestArgs {
512483
);
513484
}
514485

486+
// If exactly one test matched, we enable full tracing.
487+
if num_filtered == 1 && self.decode_internal.is_some() {
488+
runner.decode_internal = InternalTraceMode::Full;
489+
}
490+
515491
if self.json {
516492
let results = runner.test_collect(filter);
517493
println!("{}", serde_json::to_string(&results)?);

crates/forge/tests/cli/test_cmd.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2176,16 +2176,12 @@ Warning: the following cheatcode(s) are deprecated and will be removed in future
21762176
);
21772177

21782178
forgetest_init!(requires_single_test, |prj, cmd| {
2179-
cmd.args(["test", "--debug", "test"]).assert_failure().stderr_eq(str![[r#"
2179+
cmd.args(["test", "--debug"]).assert_failure().stderr_eq(str![[r#"
21802180
Error:
21812181
2 tests matched your criteria, but exactly 1 test must match in order to run the debugger.
21822182
21832183
Use --match-contract and --match-path to further limit the search.
21842184
2185-
Filter used:
2186-
match-test: `test`
2187-
2188-
21892185
"#]]);
21902186
cmd.forge_fuse().args(["test", "--flamegraph"]).assert_failure().stderr_eq(str![[r#"
21912187
Error:
@@ -2202,3 +2198,10 @@ Use --match-contract and --match-path to further limit the search.
22022198
22032199
"#]]);
22042200
});
2201+
2202+
forgetest_init!(deprecated_regex_arg, |prj, cmd| {
2203+
cmd.args(["test", "--decode-internal", "test_Increment"]).assert_success().stderr_eq(str![[r#"
2204+
warning: specifying argument for --decode-internal is deprecated and will be removed in the future, use --match-test instead
2205+
2206+
"#]]);
2207+
});

0 commit comments

Comments
 (0)