diff --git a/crates/config/README.md b/crates/config/README.md index 337195276b72..99f02e5e68f2 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -115,6 +115,10 @@ no_match_contract = "Bar" match_path = "*/Foo*" no_match_path = "*/Bar*" no_match_coverage = "Baz" +# Number of threads to use. Not set or zero specifies the number of logical cores. +threads = 0 +# whether to show test execution progress +show_progress = true ffi = false always_use_create_2_factory = false prompt_timeout = 120 diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index d6ec26b92401..89924d5c8b80 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -257,6 +257,10 @@ pub struct Config { pub coverage_pattern_inverse: Option, /// Path where last test run failures are recorded. pub test_failures_file: PathBuf, + /// Max concurrent threads to use. + pub threads: Option, + /// Whether to show test execution progress. + pub show_progress: bool, /// Configuration for fuzz testing pub fuzz: FuzzConfig, /// Configuration for invariant testing @@ -2083,6 +2087,8 @@ impl Default for Config { path_pattern_inverse: None, coverage_pattern_inverse: None, test_failures_file: "cache/test-failures".into(), + threads: None, + show_progress: false, fuzz: FuzzConfig::new("cache/fuzz".into()), invariant: InvariantConfig::new("cache/invariant".into()), always_use_create_2_factory: false, diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index fe709e028074..f80227668f20 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -85,7 +85,7 @@ pub struct TestArgs { allow_failure: bool, /// Output test results in JSON format. - #[arg(long, short, help_heading = "Display options")] + #[arg(long, help_heading = "Display options")] json: bool, /// Stop running tests after the first failure. @@ -113,8 +113,8 @@ pub struct TestArgs { /// Max concurrent threads to use. /// Default value is the number of available CPUs. - #[arg(long)] - pub max_threads: Option, + #[arg(long, short = 'j', visible_alias = "jobs")] + pub threads: Option, /// Show test execution progress. #[arg(long)] @@ -236,17 +236,17 @@ impl TestArgs { /// /// Returns the test results for all matching tests. pub async fn execute_tests(self) -> Result { + // Merge all configs. + let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; + // Set number of max threads to execute tests. // If not specified then the number of threads determined by rayon will be used. - if let Some(test_threads) = self.max_threads { + if let Some(test_threads) = config.threads { trace!(target: "forge::test", "execute tests with {} max threads", test_threads); - rayon::ThreadPoolBuilder::new().num_threads(test_threads as usize).build_global()?; + rayon::ThreadPoolBuilder::new().num_threads(test_threads).build_global()?; } - // Merge all configs - let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; - - // Explicitly enable isolation for gas reports for more correct gas accounting + // Explicitly enable isolation for gas reports for more correct gas accounting. if self.gas_report { evm_opts.isolate = true; } else { @@ -289,7 +289,7 @@ impl TestArgs { .profiles(profiles) .build(&output, project_root)?; - // Determine print verbosity and executor verbosity + // Determine print verbosity and executor verbosity. let verbosity = evm_opts.verbosity; if self.gas_report && evm_opts.verbosity < 3 { evm_opts.verbosity = 3; @@ -297,7 +297,7 @@ impl TestArgs { let env = evm_opts.evm_env().await?; - // Prepare the test builder + // Prepare the test builder. let should_debug = self.debug.is_some(); let config = Arc::new(config); let runner = MultiContractRunnerBuilder::new(config.clone()) @@ -325,7 +325,7 @@ impl TestArgs { let outcome = self.run_tests(runner, config, verbosity, &filter).await?; if should_debug { - // Get first non-empty suite result. We will have only one such entry + // Get first non-empty suite result. We will have only one such entry. let Some((_, test_result)) = outcome .results .iter() @@ -390,7 +390,7 @@ impl TestArgs { // Run tests. let (tx, rx) = channel::<(String, SuiteResult)>(); let timer = Instant::now(); - let show_progress = self.show_progress; + let show_progress = config.show_progress; let handle = tokio::task::spawn_blocking({ let filter = filter.clone(); move || runner.test(&filter, tx, show_progress) @@ -627,6 +627,14 @@ impl Provider for TestArgs { dict.insert("etherscan_api_key".to_string(), etherscan_api_key.to_string().into()); } + if self.show_progress { + dict.insert("show_progress".to_string(), true.into()); + } + + if let Some(threads) = self.threads { + dict.insert("threads".to_string(), threads.into()); + } + Ok(Map::from([(Config::selected_profile(), dict)])) } } diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index ede77beb067c..699d48caf514 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -66,6 +66,8 @@ forgetest!(can_extract_config_values, |prj, cmd| { path_pattern_inverse: None, coverage_pattern_inverse: None, test_failures_file: "test-cache/test-failures".into(), + threads: None, + show_progress: false, fuzz: FuzzConfig { runs: 1000, max_test_rejects: 100203,