Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/olive-pugs-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@nomicfoundation/edr": minor
---

Added profile support to Solidity test inline configuration. `SolidityTestRunnerConfigArgs` now accepts `testProfile` and `declaredTestProfiles`. Unprefixed directives apply to every profile, while profile-prefixed directives apply only when that profile is selected and override unprefixed directives with the same key. Undeclared profile prefixes are rejected.

BREAKING CHANGE: Renamed the `InlineConfigUnsupportedProfile` inline config problem to `InlineConfigUndeclaredProfile`. The replacement also includes the declared profile names in `declaredProfiles`.
29 changes: 24 additions & 5 deletions crates/edr_napi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,7 @@ export interface InlineConfigDirectiveLocation {
* over its `kind` tag — mirroring the Rust-side `InlineConfigError` enum so
* consumers can map each problem onto their own error types.
*/
export type InlineConfigDirectiveProblem = InlineConfigInvalidSyntax | InlineConfigUnsupportedProfile | InlineConfigInvalidKey | InlineConfigInvalidKeyForTestType | InlineConfigInvalidValue | InlineConfigDuplicateKey
export type InlineConfigDirectiveProblem = InlineConfigInvalidSyntax | InlineConfigUndeclaredProfile | InlineConfigInvalidKey | InlineConfigInvalidKeyForTestType | InlineConfigInvalidValue | InlineConfigDuplicateKey

/**
* The same key was specified more than once for the same function or
Expand Down Expand Up @@ -1024,12 +1024,14 @@ export interface InlineConfigSourceFileNotFound {
*/
export type InlineConfigSourceProblem = InlineConfigInvalidSolcVersion | InlineConfigSourceFileNotFound | InlineConfigDirectiveLocation

/** A profile other than `default` was used. */
export interface InlineConfigUnsupportedProfile {
/** A directive named a profile the project does not declare. */
export interface InlineConfigUndeclaredProfile {
/** Enum tag for JS. */
kind: "InlineConfigUnsupportedProfile"
/** The unsupported profile name. */
kind: "InlineConfigUndeclaredProfile"
/** The undeclared profile name, exactly as written. */
profile: string
/** The profiles the project declares, sorted. */
declaredProfiles: Array<string>
}

export interface InstrumentationMetadata {
Expand Down Expand Up @@ -1771,6 +1773,23 @@ export interface SolidityTestRunnerConfigArgs {
* do.
*/
importMappings?: Record<string, string>
/**
* The Solidity test profile this run was started with.
*
* An inline-config directive prefixed with a profile name
* (`forge-config: ci.fuzz.runs = 8`) applies only under that profile; an
* unprefixed one applies under every profile, and the prefixed one wins
* where both set the same key. Defaults to `default`.
*/
testProfile?: string
/**
* Every Solidity test profile the project declares.
*
* A prefix naming a profile that is not declared is an error, so a
* mistyped one fails whichever profile is selected. `default` is always
* declared. Defaults to `["default"]`.
*/
declaredTestProfiles?: Array<string>
}

export interface SourceReference {
Expand Down
22 changes: 22 additions & 0 deletions crates/edr_napi/src/solidity_tests/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use edr_primitives::hex;
use edr_solidity_tests::{
executors::invariant::InvariantConfig,
fuzz::FuzzConfig,
inline_config::DEFAULT_PROFILE,
inspectors::cheatcodes::{CheatsConfigOptions, ExecutionContextConfig},
TestFilterConfig,
};
Expand Down Expand Up @@ -223,6 +224,19 @@ pub struct SolidityTestRunnerConfigArgs<'env> {
/// file and need no entry here; only non-relative paths (package imports)
/// do.
pub import_mappings: Option<HashMap<String, String>>,
/// The Solidity test profile this run was started with.
///
/// An inline-config directive prefixed with a profile name
/// (`forge-config: ci.fuzz.runs = 8`) applies only under that profile; an
/// unprefixed one applies under every profile, and the prefixed one wins
/// where both set the same key. Defaults to `default`.
pub test_profile: Option<String>,
/// Every Solidity test profile the project declares.
///
/// A prefix naming a profile that is not declared is an error, so a
/// mistyped one fails whichever profile is selected. `default` is always
/// declared. Defaults to `["default"]`.
pub declared_test_profiles: Option<Vec<String>>,
}

impl SolidityTestRunnerConfigArgs<'_> {
Expand Down Expand Up @@ -274,6 +288,8 @@ impl SolidityTestRunnerConfigArgs<'_> {
eip712_canonical_types,
test_source_paths,
import_mappings,
test_profile,
declared_test_profiles,
} = self;

let test_source_paths = test_source_paths.map_or_else(HashMap::new, |paths| {
Expand All @@ -290,6 +306,10 @@ impl SolidityTestRunnerConfigArgs<'_> {
.collect()
});

let test_profile = test_profile.unwrap_or_else(|| DEFAULT_PROFILE.to_owned());
let declared_test_profiles =
declared_test_profiles.unwrap_or_else(|| vec![DEFAULT_PROFILE.to_owned()]);

let test_pattern = TestFilterConfig {
test_pattern: test_pattern
.as_ref()
Expand Down Expand Up @@ -417,6 +437,8 @@ impl SolidityTestRunnerConfigArgs<'_> {
generate_gas_report,
test_source_paths,
import_mappings,
test_profile,
declared_test_profiles,
};

Ok(config)
Expand Down
19 changes: 11 additions & 8 deletions crates/edr_napi/src/solidity_tests/inline_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ pub struct InlineConfigInvalidSyntax {
pub directive: String,
}

/// A profile other than `default` was used.
/// A directive named a profile the project does not declare.
#[napi(object)]
pub struct InlineConfigUnsupportedProfile {
pub struct InlineConfigUndeclaredProfile {
/// Enum tag for JS.
#[napi(ts_type = "\"InlineConfigUnsupportedProfile\"")]
#[napi(ts_type = "\"InlineConfigUndeclaredProfile\"")]
pub kind: String,
/// The unsupported profile name.
/// The undeclared profile name, exactly as written.
pub profile: String,
/// The profiles the project declares, sorted.
pub declared_profiles: Vec<String>,
}

/// An unknown configuration key was used.
Expand Down Expand Up @@ -133,7 +135,7 @@ pub type InlineConfigSourceProblem = Either3<
#[napi]
pub type InlineConfigDirectiveProblem = Either6<
InlineConfigInvalidSyntax,
InlineConfigUnsupportedProfile,
InlineConfigUndeclaredProfile,
InlineConfigInvalidKey,
InlineConfigInvalidKeyForTestType,
InlineConfigInvalidValue,
Expand Down Expand Up @@ -216,10 +218,11 @@ fn to_directive_problem(error: &CoreInlineConfigError) -> InlineConfigDirectiveP
kind: "InlineConfigInvalidSyntax".to_owned(),
directive: line.clone(),
}),
CoreInlineConfigError::UnsupportedProfile { profile } => {
Either6::B(InlineConfigUnsupportedProfile {
kind: "InlineConfigUnsupportedProfile".to_owned(),
CoreInlineConfigError::UndeclaredProfile { profile, declared } => {
Either6::B(InlineConfigUndeclaredProfile {
kind: "InlineConfigUndeclaredProfile".to_owned(),
profile: profile.clone(),
declared_profiles: declared.clone(),
})
}
CoreInlineConfigError::InvalidKey { key } => Either6::C(InlineConfigInvalidKey {
Expand Down
21 changes: 20 additions & 1 deletion crates/edr_napi_core/src/solidity/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use edr_solidity_tests::{
backend::Predeploy,
evm_context::HardforkTr,
fuzz::{invariant::InvariantConfig, FuzzConfig},
inline_config::ImportResolver,
inline_config::{ImportResolver, InlineConfigProfiles},
inspectors::cheatcodes::CheatsConfigOptions,
opts::effective_transaction_gas_cap,
CollectStackTraces, SolidityTestRunnerConfig, SyncOnCollectedCoverageCallback,
Expand Down Expand Up @@ -205,6 +205,14 @@ pub struct TestRunnerConfig {
/// (`./`, `../`) are resolved against the importing file and need no entry
/// here.
pub import_mappings: HashMap<String, PathBuf>,
/// The Solidity test profile the run was started with. Inline-config
/// directives prefixed with a profile name apply only when that profile is
/// the selected one.
pub test_profile: String,
/// Every Solidity test profile the project declares. A profile-prefixed
/// inline-config directive naming an undeclared profile is an error,
/// whichever profile is selected.
pub declared_test_profiles: Vec<String>,
}

fn parse_hardfork<HardforkT>(hardfork: String) -> napi::Result<HardforkT>
Expand Down Expand Up @@ -265,6 +273,8 @@ impl TestRunnerConfig {
generate_gas_report,
test_source_paths,
import_mappings,
test_profile,
declared_test_profiles,
} = self;

let mut evm_opts = SolidityTestRunnerConfig::default_evm_opts();
Expand Down Expand Up @@ -365,6 +375,10 @@ impl TestRunnerConfig {

let import_resolver = ImportResolver::new(import_mappings);

let inline_config_profiles =
InlineConfigProfiles::new(test_profile, declared_test_profiles)
.map_err(|error| napi::Error::new(napi::Status::InvalidArg, error.to_string()))?;

Ok(SolidityTestRunnerConfig {
project_root,
collect_stack_traces,
Expand All @@ -383,12 +397,15 @@ impl TestRunnerConfig {
generate_gas_report,
test_source_paths,
import_resolver,
inline_config_profiles,
})
}
}

#[cfg(test)]
mod tests {
use edr_solidity_tests::inline_config::DEFAULT_PROFILE;

use super::*;

fn default_config() -> TestRunnerConfig {
Expand Down Expand Up @@ -429,6 +446,8 @@ mod tests {
generate_gas_report: None,
test_source_paths: HashMap::new(),
import_mappings: HashMap::new(),
test_profile: DEFAULT_PROFILE.to_owned(),
declared_test_profiles: vec![DEFAULT_PROFILE.to_owned()],
}
}

Expand Down
6 changes: 5 additions & 1 deletion crates/edr_solidity_tests/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use foundry_evm::{

use crate::{
fork::CreateFork,
inline_config::{ImportResolver, InlineConfigErrors},
inline_config::{ImportResolver, InlineConfigErrors, InlineConfigProfiles},
opts::{effective_transaction_gas_cap, Env as EvmEnv, EvmOpts},
};

Expand Down Expand Up @@ -72,6 +72,10 @@ pub struct SolidityTestRunnerConfig<HardforkT: HardforkTr> {
/// Resolves the imports of test sources when parsing their inline
/// configuration (`forge-config:`/`hardhat-config:` NatSpec directives).
pub import_resolver: ImportResolver,
/// The profile the run was started with and every profile the project
/// declares, used to scope inline-config directives. Defaults to the single
/// `default` profile.
pub inline_config_profiles: InlineConfigProfiles,
}

impl<HardforkT: HardforkTr> SolidityTestRunnerConfig<HardforkT> {
Expand Down
25 changes: 24 additions & 1 deletion crates/edr_solidity_tests/src/inline_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@
//!
//! Both the `forge-config:` and `hardhat-config:` prefixes are recognized.
//!
//! A directive may be scoped to a test profile by prefixing its key with the
//! profile's name. A prefixed directive applies only under that profile, an
//! unprefixed one under every profile, and where both set the same key the
//! prefixed one wins, whatever order they appear in. A prefix must name a
//! declared profile — see [`InlineConfigProfiles`].
//!
//! ```solidity
//! /// forge-config: fuzz.runs = 3 // under every profile
//! /// forge-config: ci.fuzz.runs = 500 // only under `ci`
//! function testFoo(uint256 x) public { /* ... */ }
//! ```
//!
//! Profiles are resolved separately for the contracts and functions,
//! and only then are the contract's directives applied underneath the
//! function's. Level precedence therefore outranks profile precedence: a
Comment thread
Wodann marked this conversation as resolved.
Resolved
//! function's unprefixed `fuzz.runs` beats the contract's `ci.fuzz.runs` even
//! under `ci`.
//!
//! The work flows through the submodules as a pipeline:
//!
//! ```text
Expand All @@ -28,22 +46,27 @@
//! - overrides compose the above into a source's per-contract overrides
//! - provider cache the overrides and serve them
//! ```
//!
//! `profiles` carries the run's selected and declared profiles through that
//! pipeline, so `directives` can scope each one.

mod directives;
mod error;
mod natspec;
mod overrides;
mod parse;
mod profiles;
mod provider;
mod resolver;

pub(crate) use self::directives::is_test_function;
pub use self::{
error::{
InlineConfigCollectError, InlineConfigError, InlineConfigErrorItem, InlineConfigErrors,
InlineConfigProblem,
InlineConfigProblem, InlineConfigProfilesError,
},
overrides::{ContractInlineConfig, FunctionOverride},
profiles::{InlineConfigProfiles, DEFAULT_PROFILE},
provider::{CachedInlineConfigProvider, InlineConfigRoot, SharedInlineConfigProvider},
resolver::ImportResolver,
};
Loading
Loading