Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 28 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,28 @@
//!
//! 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 { /* ... */ }
//! ```
//!
//! Directives are handled in this order of priority:
//! 1. Function-level > Contract-level
//! 2. Profile > bare
//!
//! Therefore, function-level directives always take precedence over
//! contract-level ones, and profile-specific directives take precedence over
//! unprefixed ones within the same level.
//!
//! Custom profiles do not inherit from the "default" profile.
//!
//! The work flows through the submodules as a pipeline:
//!
//! ```text
Expand All @@ -28,22 +50,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