Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Vyper support for forge test #7981

Closed
wants to merge 3 commits into from
Closed
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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ pub struct ContractSources {

impl ContractSources {
/// Collects the contract sources and artifacts from the project compile output.
pub fn from_project_output(
output: &ProjectCompileOutput,
pub fn from_project_output<E>(
output: &ProjectCompileOutput<E>,
root: &Path,
libraries: &Libraries,
) -> Result<ContractSources> {
Expand Down
2 changes: 1 addition & 1 deletion crates/config/src/inline/natspec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl NatSpec {
/// Factory function that extracts a vector of [`NatSpec`] instances from
/// a solc compiler output. The root path is to express contract base dirs.
/// That is essential to match per-test configs at runtime.
pub fn parse(output: &ProjectCompileOutput, root: &Path) -> Vec<Self> {
pub fn parse<E>(output: &ProjectCompileOutput<E>, root: &Path) -> Vec<Self> {
let mut natspecs: Vec<Self> = vec![];

let solc = SolcParser::new();
Expand Down
20 changes: 19 additions & 1 deletion crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,7 @@ impl Config {
compiler_config: CompilerConfig<C>,
settings: C::Settings,
) -> Result<Project<C>, SolcError> {
let project = ProjectBuilder::<ConfigurableArtifacts, C>::new(Default::default())
let project = ProjectBuilder::<C>::new(Default::default())
.artifacts(self.configured_artifacts_handler())
.paths(self.project_paths())
.settings(settings)
Expand Down Expand Up @@ -2799,6 +2799,24 @@ macro_rules! with_resolved_project {
};
}

/// Helper trait to resolve project depending on [Compiler] generic.
pub trait ResolveProject<C: Compiler> {
/// Returns configured project.
fn resolve_project(&self) -> Result<Project<C>, SolcError>;
}

impl ResolveProject<Solc> for Config {
fn resolve_project(&self) -> Result<Project<Solc>, SolcError> {
self.project()
}
}

impl ResolveProject<Vyper> for Config {
fn resolve_project(&self) -> Result<Project<Vyper>, SolcError> {
self.vyper_project()
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion crates/forge/bin/cmd/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl BuildArgs {
with_resolved_project!(config, |project| {
let project = project?;

let filter = if let Some(ref skip) = self.skip {
let filter = if let Some(skip) = &self.skip {
if !skip.is_empty() {
let filter = SkipBuildFilters::new(skip.clone(), project.root().clone())?;
Some(filter)
Expand Down
33 changes: 31 additions & 2 deletions crates/forge/bin/cmd/test/filter.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
use clap::Parser;
use forge::TestFilter;
use foundry_common::glob::GlobMatcher;
use foundry_compilers::{FileFilter, ProjectPathsConfig};
use foundry_compilers::{
compilers::vyper::parser::VyperParsedSource,
resolver::{parse::SolData, GraphEdges},
FileFilter, ProjectPathsConfig, SolcSparseFileFilter, SparseOutputFileFilter,
};
use foundry_config::Config;
use std::{fmt, path::Path};
use std::{
fmt,
path::{Path, PathBuf},
};

/// The filter to use during testing.
///
Expand Down Expand Up @@ -214,3 +221,25 @@ impl fmt::Display for ProjectPathsAwareFilter {
self.args_filter.fmt(f)
}
}

impl FileFilter for &ProjectPathsAwareFilter {
fn is_match(&self, file: &Path) -> bool {
(*self).is_match(file)
}
}

impl SparseOutputFileFilter<SolData> for ProjectPathsAwareFilter {
fn sparse_sources(&self, file: &Path, graph: &GraphEdges<SolData>) -> Vec<PathBuf> {
SolcSparseFileFilter::new(self).sparse_sources(file, graph)
}
}

impl SparseOutputFileFilter<VyperParsedSource> for ProjectPathsAwareFilter {
fn sparse_sources(&self, file: &Path, _graph: &GraphEdges<VyperParsedSource>) -> Vec<PathBuf> {
if self.is_match(file) {
vec![file.to_path_buf()]
} else {
vec![]
}
}
}
62 changes: 41 additions & 21 deletions crates/forge/bin/cmd/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use forge::{
decode::decode_console_logs,
gas_report::GasReport,
multi_runner::matches_contract,
opts::EvmOpts,
result::{SuiteResult, TestOutcome, TestStatus},
traces::{identifier::SignaturesIdentifier, CallTraceDecoderBuilder, TraceKind},
MultiContractRunner, MultiContractRunnerBuilder, TestFilter, TestOptions, TestOptionsBuilder,
Expand All @@ -20,16 +21,18 @@ use foundry_common::{
shell,
};
use foundry_compilers::{
artifacts::output_selection::OutputSelection, utils::source_files_iter, SolcSparseFileFilter,
SOLC_EXTENSIONS,
artifacts::output_selection::OutputSelection,
compilers::{CompilationError, Compiler, CompilerSettings},
utils::source_files_iter,
Project, SparseOutputFileFilter,
};
use foundry_config::{
figment,
figment::{
self,
value::{Dict, Map},
Metadata, Profile, Provider,
},
get_available_profiles, Config,
get_available_profiles, with_resolved_project, Config,
};
use foundry_debugger::Debugger;
use foundry_evm::traces::identifier::TraceIdentifiers;
Expand Down Expand Up @@ -146,16 +149,21 @@ impl TestArgs {
/// Returns sources which include any tests to be executed.
/// If no filters are provided, sources are filtered by existence of test/invariant methods in
/// them, If filters are provided, sources are additionaly filtered by them.
pub fn get_sources_to_compile(
pub fn get_sources_to_compile<C>(
&self,
config: &Config,
project: &Project<C>,
filter: &ProjectPathsAwareFilter,
) -> Result<BTreeSet<PathBuf>> {
let mut project = config.create_project(true, true)?;
project.settings.output_selection =
) -> Result<BTreeSet<PathBuf>>
where
C: Compiler,
ProjectPathsAwareFilter: SparseOutputFileFilter<C::ParsedSource>,
{
let mut project = project.clone();
*project.settings.output_selection_mut() =
OutputSelection::common_output_selection(["abi".to_string()]);
project.no_artifacts = true;

let output = project.compile_sparse(Box::new(SolcSparseFileFilter::new(filter.clone())))?;
let output = project.compile_sparse(Box::new(filter.clone()))?;

if output.has_compiler_errors() {
println!("{}", output);
Expand Down Expand Up @@ -205,7 +213,7 @@ impl TestArgs {
}

// Always recompile all sources to ensure that `getCode` cheatcode can use any artifact.
test_sources.extend(source_files_iter(project.paths.sources, SOLC_EXTENSIONS));
test_sources.extend(source_files_iter(project.paths.sources, C::FILE_EXTENSIONS));

Ok(test_sources)
}
Expand All @@ -229,24 +237,36 @@ impl TestArgs {
config.invariant.gas_report_samples = 0;
}

// Set up the project.
let mut project = config.project()?;

// Install missing dependencies.
if install::install_missing_dependencies(&mut config, self.build_args().silent) &&
config.auto_detect_remappings
{
// need to re-configure here to also catch additional remappings
config = self.load_config();
project = config.project()?;
}

with_resolved_project!(config, |project| {
self.run_with_project(config, evm_opts, project?).await
})
}

pub async fn run_with_project<C>(
&self,
config: Config,
mut evm_opts: EvmOpts,
project: Project<C>,
) -> eyre::Result<TestOutcome>
where
C: Compiler,
C::CompilationError: Clone,
ProjectPathsAwareFilter: SparseOutputFileFilter<C::ParsedSource>,
{
let mut filter = self.filter(&config);
trace!(target: "forge::test", ?filter, "using filter");

let sources_to_compile = self.get_sources_to_compile(&config, &filter)?;
let sources_to_compile = self.get_sources_to_compile(&project, &filter)?;

let compiler = ProjectCompiler::new()
let compiler = ProjectCompiler::<C>::new()
.quiet_if(self.json || self.opts.silent)
.files(sources_to_compile);

Expand Down Expand Up @@ -335,9 +355,9 @@ impl TestArgs {
}

/// Run all tests that matches the filter predicate from a test runner
pub async fn run_tests(
pub async fn run_tests<E: CompilationError>(
&self,
mut runner: MultiContractRunner,
mut runner: MultiContractRunner<E>,
config: Arc<Config>,
verbosity: u8,
filter: &ProjectPathsAwareFilter,
Expand Down Expand Up @@ -603,8 +623,8 @@ impl Provider for TestArgs {
}

/// Lists all matching tests
fn list(
runner: MultiContractRunner,
fn list<E: CompilationError>(
runner: MultiContractRunner<E>,
filter: &ProjectPathsAwareFilter,
json: bool,
) -> Result<TestOutcome> {
Expand Down
8 changes: 4 additions & 4 deletions crates/forge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ pub struct TestOptions {
impl TestOptions {
/// Tries to create a new instance by detecting inline configurations from the project compile
/// output.
pub fn new(
output: &ProjectCompileOutput,
pub fn new<E>(
output: &ProjectCompileOutput<E>,
root: &Path,
profiles: Vec<String>,
base_fuzz: FuzzConfig,
Expand Down Expand Up @@ -201,9 +201,9 @@ impl TestOptionsBuilder {
/// `root` is a reference to the user's project root dir. This is essential
/// to determine the base path of generated contract identifiers. This is to provide correct
/// matchers for inline test configs.
pub fn build(
pub fn build<E>(
self,
output: &ProjectCompileOutput,
output: &ProjectCompileOutput<E>,
root: &Path,
) -> Result<TestOptions, InlineConfigError> {
let profiles: Vec<String> =
Expand Down
17 changes: 10 additions & 7 deletions crates/forge/src/multi_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use alloy_json_abi::{Function, JsonAbi};
use alloy_primitives::{Address, Bytes, U256};
use eyre::Result;
use foundry_common::{get_contract_name, ContractsByArtifact, TestFunctionExt};
use foundry_compilers::{artifacts::Libraries, Artifact, ArtifactId, ProjectCompileOutput, Solc};
use foundry_compilers::{
artifacts::Libraries, compilers::CompilationError, Artifact, ArtifactId, ProjectCompileOutput,
Solc,
};
use foundry_config::Config;
use foundry_evm::{
backend::Backend, decode::RevertDecoder, executors::ExecutorBuilder, fork::CreateFork,
Expand Down Expand Up @@ -35,7 +38,7 @@ pub type DeployableContracts = BTreeMap<ArtifactId, TestContract>;

/// A multi contract runner receives a set of contracts deployed in an EVM instance and proceeds
/// to run all test functions in these contracts.
pub struct MultiContractRunner {
pub struct MultiContractRunner<E> {
/// Mapping of contract name to JsonAbi, creation bytecode and library bytecode which
/// needs to be deployed & linked against
pub contracts: DeployableContracts,
Expand All @@ -62,10 +65,10 @@ pub struct MultiContractRunner {
/// Whether to enable call isolation
pub isolation: bool,
/// Output of the project compilation
pub output: ProjectCompileOutput,
pub output: ProjectCompileOutput<E>,
}

impl MultiContractRunner {
impl<E: CompilationError> MultiContractRunner<E> {
/// Returns an iterator over all contracts that match the filter.
pub fn matching_contracts<'a>(
&'a self,
Expand Down Expand Up @@ -315,13 +318,13 @@ impl MultiContractRunnerBuilder {

/// Given an EVM, proceeds to return a runner which is able to execute all tests
/// against that evm
pub fn build(
pub fn build<E: CompilationError>(
self,
root: &Path,
output: ProjectCompileOutput,
output: ProjectCompileOutput<E>,
env: revm::primitives::Env,
evm_opts: EvmOpts,
) -> Result<MultiContractRunner> {
) -> Result<MultiContractRunner<E>> {
let output = output.with_stripped_file_prefixes(root);
let linker = Linker::new(root, output.artifact_ids().collect());

Expand Down
10 changes: 8 additions & 2 deletions crates/forge/tests/it/cheats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ use crate::{
config::*,
test_helpers::{
ForgeTestData, RE_PATH_SEPARATOR, TEST_DATA_CANCUN, TEST_DATA_DEFAULT,
TEST_DATA_MULTI_VERSION,
TEST_DATA_MULTI_VERSION, TEST_DATA_VYPER,
},
};
use foundry_compilers::compilers::Compiler;
use foundry_config::{fs_permissions::PathPermission, FsPermissions};
use foundry_test_utils::Filter;

/// Executes all cheat code tests but not fork cheat codes or tests that require isolation mode
async fn test_cheats_local(test_data: &ForgeTestData) {
async fn test_cheats_local<C: Compiler>(test_data: &ForgeTestData<C>) {
let mut filter = Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}*"))
.exclude_paths("Fork")
.exclude_contracts("Isolated");
Expand Down Expand Up @@ -58,3 +59,8 @@ async fn test_cheats_local_multi_version() {
async fn test_cheats_local_cancun() {
test_cheats_local(&TEST_DATA_CANCUN).await
}

#[tokio::test(flavor = "multi_thread")]
async fn test_cheats_local_vyper() {
test_cheats_local(&TEST_DATA_VYPER).await
}
11 changes: 6 additions & 5 deletions crates/forge/tests/it/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use forge::{
result::{SuiteResult, TestStatus},
MultiContractRunner,
};
use foundry_compilers::compilers::CompilationError;
use foundry_evm::{
decode::decode_console_logs,
revm::primitives::SpecId,
Expand All @@ -15,18 +16,18 @@ use itertools::Itertools;
use std::collections::BTreeMap;

/// How to execute a test run.
pub struct TestConfig {
pub runner: MultiContractRunner,
pub struct TestConfig<E> {
pub runner: MultiContractRunner<E>,
pub should_fail: bool,
pub filter: Filter,
}

impl TestConfig {
pub fn new(runner: MultiContractRunner) -> Self {
impl<E: CompilationError> TestConfig<E> {
pub fn new(runner: MultiContractRunner<E>) -> Self {
Self::with_filter(runner, Filter::matches_all())
}

pub fn with_filter(runner: MultiContractRunner, filter: Filter) -> Self {
pub fn with_filter(runner: MultiContractRunner<E>, filter: Filter) -> Self {
init_tracing();
Self { runner, should_fail: false, filter }
}
Expand Down
2 changes: 1 addition & 1 deletion crates/forge/tests/it/repros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async fn repro_config(
should_fail: bool,
sender: Option<Address>,
test_data: &ForgeTestData,
) -> TestConfig {
) -> TestConfig<foundry_compilers::artifacts::Error> {
foundry_test_utils::init_tracing();
let filter = Filter::path(&format!(".*repros/Issue{issue}.t.sol"));

Expand Down
Loading
Loading