mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-06-13 11:41:03 +00:00
refactor existing dt infra
This commit is contained in:
@@ -52,6 +52,15 @@ impl AsRef<WorkingDirectoryConfiguration> for Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<CorpusConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &CorpusConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AsRef<SolcConfiguration> for Context {
|
impl AsRef<SolcConfiguration> for Context {
|
||||||
fn as_ref(&self) -> &SolcConfiguration {
|
fn as_ref(&self) -> &SolcConfiguration {
|
||||||
match self {
|
match self {
|
||||||
@@ -183,9 +192,9 @@ pub struct TestExecutionContext {
|
|||||||
)]
|
)]
|
||||||
pub platforms: Vec<PlatformIdentifier>,
|
pub platforms: Vec<PlatformIdentifier>,
|
||||||
|
|
||||||
/// A list of test corpus JSON files to be tested.
|
/// Configuration parameters for the corpus files to use.
|
||||||
#[arg(long = "corpus", short)]
|
#[clap(flatten, next_help_heading = "Corpus Configuration")]
|
||||||
pub corpus: Vec<PathBuf>,
|
pub corpus_configuration: CorpusConfiguration,
|
||||||
|
|
||||||
/// Configuration parameters for the solc compiler.
|
/// Configuration parameters for the solc compiler.
|
||||||
#[clap(flatten, next_help_heading = "Solc Configuration")]
|
#[clap(flatten, next_help_heading = "Solc Configuration")]
|
||||||
@@ -259,9 +268,9 @@ pub struct BenchmarkingContext {
|
|||||||
)]
|
)]
|
||||||
pub platforms: Vec<PlatformIdentifier>,
|
pub platforms: Vec<PlatformIdentifier>,
|
||||||
|
|
||||||
/// A list of test corpus JSON files to be tested.
|
/// Configuration parameters for the corpus files to use.
|
||||||
#[arg(long = "corpus", short)]
|
#[clap(flatten, next_help_heading = "Corpus Configuration")]
|
||||||
pub corpus: Vec<PathBuf>,
|
pub corpus_configuration: CorpusConfiguration,
|
||||||
|
|
||||||
/// Configuration parameters for the solc compiler.
|
/// Configuration parameters for the solc compiler.
|
||||||
#[clap(flatten, next_help_heading = "Solc Configuration")]
|
#[clap(flatten, next_help_heading = "Solc Configuration")]
|
||||||
@@ -320,6 +329,12 @@ impl AsRef<WorkingDirectoryConfiguration> for TestExecutionContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<CorpusConfiguration> for TestExecutionContext {
|
||||||
|
fn as_ref(&self) -> &CorpusConfiguration {
|
||||||
|
&self.corpus_configuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AsRef<SolcConfiguration> for TestExecutionContext {
|
impl AsRef<SolcConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &SolcConfiguration {
|
fn as_ref(&self) -> &SolcConfiguration {
|
||||||
&self.solc_configuration
|
&self.solc_configuration
|
||||||
@@ -404,6 +419,12 @@ impl AsRef<WorkingDirectoryConfiguration> for BenchmarkingContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<CorpusConfiguration> for BenchmarkingContext {
|
||||||
|
fn as_ref(&self) -> &CorpusConfiguration {
|
||||||
|
&self.corpus_configuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AsRef<SolcConfiguration> for BenchmarkingContext {
|
impl AsRef<SolcConfiguration> for BenchmarkingContext {
|
||||||
fn as_ref(&self) -> &SolcConfiguration {
|
fn as_ref(&self) -> &SolcConfiguration {
|
||||||
&self.solc_configuration
|
&self.solc_configuration
|
||||||
@@ -470,6 +491,14 @@ impl AsRef<ReportConfiguration> for BenchmarkingContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A set of configuration parameters for the corpus files to use for the execution.
|
||||||
|
#[derive(Clone, Debug, Parser, Serialize)]
|
||||||
|
pub struct CorpusConfiguration {
|
||||||
|
/// A list of test corpus JSON files to be tested.
|
||||||
|
#[arg(long = "corpus", short)]
|
||||||
|
pub paths: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
/// A set of configuration parameters for Solc.
|
/// A set of configuration parameters for Solc.
|
||||||
#[derive(Clone, Debug, Parser, Serialize)]
|
#[derive(Clone, Debug, Parser, Serialize)]
|
||||||
pub struct SolcConfiguration {
|
pub struct SolcConfiguration {
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloy::{
|
||||||
|
network::{Ethereum, TransactionBuilder},
|
||||||
|
rpc::types::TransactionRequest,
|
||||||
|
};
|
||||||
|
use anyhow::{Context as _, Result};
|
||||||
|
use futures::{StreamExt, stream};
|
||||||
|
use revive_dt_common::types::{PlatformIdentifier, PrivateKeyAllocator};
|
||||||
|
use revive_dt_format::{
|
||||||
|
metadata::ContractPathAndIdent,
|
||||||
|
steps::{FunctionCallStep, Step},
|
||||||
|
};
|
||||||
|
use tracing::{debug, error};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
differential_tests::ExecutionState,
|
||||||
|
helpers::{CachedCompiler, TestDefinition},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct DifferentialTestsDriver<'a> {
|
||||||
|
/// The definition of the test that the driver is instructed to execute.
|
||||||
|
test_definition: TestDefinition<'a>,
|
||||||
|
|
||||||
|
/// The private key allocator used by this driver and other drivers when account allocations are
|
||||||
|
/// needed.
|
||||||
|
private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>,
|
||||||
|
|
||||||
|
/// The execution state associated with each one of the platforms that the test definition
|
||||||
|
/// is instructed to run on.
|
||||||
|
execution_state: BTreeMap<PlatformIdentifier, ExecutionState>,
|
||||||
|
// TODO(driver-refactor): Explore the idea of keeping an iterator over the steps in here that
|
||||||
|
// provides both the steps and the step path.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DifferentialTestsDriver<'a> {
|
||||||
|
pub async fn new(
|
||||||
|
test_definition: TestDefinition<'a>,
|
||||||
|
private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>,
|
||||||
|
cached_compiler: &CachedCompiler<'a>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let mut this = DifferentialTestsDriver {
|
||||||
|
test_definition,
|
||||||
|
private_key_allocator,
|
||||||
|
execution_state: Default::default(),
|
||||||
|
};
|
||||||
|
this.init_state(cached_compiler)
|
||||||
|
.await
|
||||||
|
.context("Failed to initialize the state of the differential tests driver")?;
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init_state(&mut self, cached_compiler: &CachedCompiler<'a>) -> Result<()> {
|
||||||
|
let test_definition = &self.test_definition;
|
||||||
|
self.execution_state = stream::iter(self.test_definition.platforms.iter())
|
||||||
|
// Compiling the pre-link contracts.
|
||||||
|
.filter_map(|(platform_identifier, platform_information)| async move {
|
||||||
|
let compiler_output = cached_compiler
|
||||||
|
.compile_contracts(
|
||||||
|
test_definition.metadata,
|
||||||
|
test_definition.metadata_file_path,
|
||||||
|
test_definition.mode.clone(),
|
||||||
|
None,
|
||||||
|
platform_information.compiler.as_ref(),
|
||||||
|
platform_information.platform,
|
||||||
|
&platform_information.reporter,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
error!(
|
||||||
|
?err,
|
||||||
|
%platform_identifier,
|
||||||
|
"Pre-linking compilation failed"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
Some((platform_identifier, platform_information, compiler_output))
|
||||||
|
})
|
||||||
|
// Deploying the libraries for the platform.
|
||||||
|
.filter_map(
|
||||||
|
|(platform_identifier, platform_information, compiler_output)| async move {
|
||||||
|
let mut deployed_libraries = None::<HashMap<_, _>>;
|
||||||
|
let mut contract_sources = test_definition
|
||||||
|
.metadata
|
||||||
|
.contract_sources()
|
||||||
|
.inspect_err(|err| {
|
||||||
|
error!(
|
||||||
|
?err,
|
||||||
|
%platform_identifier,
|
||||||
|
"Failed to retrieve contract sources from metadata"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
for library_instance in test_definition
|
||||||
|
.metadata
|
||||||
|
.libraries
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.flat_map(|(_, map)| map.values())
|
||||||
|
{
|
||||||
|
debug!(%library_instance, "Deploying Library Instance");
|
||||||
|
|
||||||
|
let ContractPathAndIdent {
|
||||||
|
contract_source_path: library_source_path,
|
||||||
|
contract_ident: library_ident,
|
||||||
|
} = contract_sources.remove(library_instance)?;
|
||||||
|
|
||||||
|
let (code, abi) = compiler_output
|
||||||
|
.contracts
|
||||||
|
.get(&library_source_path)
|
||||||
|
.and_then(|contracts| contracts.get(library_ident.as_str()))?;
|
||||||
|
|
||||||
|
let code = alloy::hex::decode(code).ok()?;
|
||||||
|
|
||||||
|
// Getting the deployer address from the cases themselves. This is to ensure
|
||||||
|
// that we're doing the deployments from different accounts and therefore we're
|
||||||
|
// not slowed down by the nonce.
|
||||||
|
let deployer_address = test_definition
|
||||||
|
.case
|
||||||
|
.steps
|
||||||
|
.iter()
|
||||||
|
.filter_map(|step| match step {
|
||||||
|
Step::FunctionCall(input) => input.caller.as_address().copied(),
|
||||||
|
Step::BalanceAssertion(..) => None,
|
||||||
|
Step::StorageEmptyAssertion(..) => None,
|
||||||
|
Step::Repeat(..) => None,
|
||||||
|
Step::AllocateAccount(..) => None,
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.unwrap_or(FunctionCallStep::default_caller_address());
|
||||||
|
let tx = TransactionBuilder::<Ethereum>::with_deploy_code(
|
||||||
|
TransactionRequest::default().from(deployer_address),
|
||||||
|
code,
|
||||||
|
);
|
||||||
|
let receipt = platform_information
|
||||||
|
.node
|
||||||
|
.execute_transaction(tx)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
error!(
|
||||||
|
?err,
|
||||||
|
%library_instance,
|
||||||
|
%platform_identifier,
|
||||||
|
"Failed to deploy the library"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
?library_instance,
|
||||||
|
%platform_identifier,
|
||||||
|
"Deployed library"
|
||||||
|
);
|
||||||
|
|
||||||
|
let library_address = receipt.contract_address?;
|
||||||
|
|
||||||
|
deployed_libraries.get_or_insert_default().insert(
|
||||||
|
library_instance.clone(),
|
||||||
|
(library_ident.clone(), library_address, abi.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((
|
||||||
|
platform_identifier,
|
||||||
|
platform_information,
|
||||||
|
compiler_output,
|
||||||
|
deployed_libraries,
|
||||||
|
))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// Compiling the post-link contracts.
|
||||||
|
.filter_map(
|
||||||
|
|(platform_identifier, platform_information, _, deployed_libraries)| async move {
|
||||||
|
let compiler_output = cached_compiler
|
||||||
|
.compile_contracts(
|
||||||
|
test_definition.metadata,
|
||||||
|
test_definition.metadata_file_path,
|
||||||
|
test_definition.mode.clone(),
|
||||||
|
deployed_libraries.as_ref(),
|
||||||
|
platform_information.compiler.as_ref(),
|
||||||
|
platform_information.platform,
|
||||||
|
&platform_information.reporter,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
error!(
|
||||||
|
?err,
|
||||||
|
%platform_identifier,
|
||||||
|
"Pre-linking compilation failed"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let state = ExecutionState::new(
|
||||||
|
compiler_output.contracts,
|
||||||
|
deployed_libraries.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Some((*platform_identifier, state))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// Collect
|
||||||
|
.collect::<BTreeMap<_, _>>()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
//! The main entry point into differential testing.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use revive_dt_core::Platform;
|
||||||
|
use tracing::{error, instrument};
|
||||||
|
|
||||||
|
use revive_dt_config::{Context, TestExecutionContext};
|
||||||
|
use revive_dt_report::Reporter;
|
||||||
|
|
||||||
|
use crate::helpers::{NodePool, collect_metadata_files};
|
||||||
|
|
||||||
|
/// Handles the differential testing executing it according to the information defined in the
|
||||||
|
/// context
|
||||||
|
#[instrument(level = "info", err(Debug), skip_all)]
|
||||||
|
pub async fn handle_differential_tests(
|
||||||
|
context: TestExecutionContext,
|
||||||
|
reporter: Reporter,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// Discover all of the metadata files that are defined in the context.
|
||||||
|
let metadata_files = collect_metadata_files(&context)
|
||||||
|
.context("Failed to collect metadata files for differential testing")?;
|
||||||
|
|
||||||
|
// Discover the list of platforms that the tests should run on based on the context.
|
||||||
|
let platforms = context
|
||||||
|
.platforms
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(Into::<&dyn Platform>::into)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Starting the nodes of the various platforms specified in the context.
|
||||||
|
let platforms_and_nodes = {
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
|
||||||
|
for platform in platforms.iter() {
|
||||||
|
let platform_identifier = platform.platform_identifier();
|
||||||
|
|
||||||
|
let context = Context::ExecuteTests(Box::new(context.clone()));
|
||||||
|
let node_pool = NodePool::new(context, *platform)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
error!(
|
||||||
|
?err,
|
||||||
|
%platform_identifier,
|
||||||
|
"Failed to initialize the node pool for the platform."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.context("Failed to initialize the node pool")?;
|
||||||
|
|
||||||
|
map.insert(platform_identifier, (*platform, node_pool));
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
|
};
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
|
use alloy::{
|
||||||
|
json_abi::JsonAbi,
|
||||||
|
primitives::{Address, U256},
|
||||||
|
};
|
||||||
|
|
||||||
|
use revive_dt_format::metadata::{ContractIdent, ContractInstance};
|
||||||
|
|
||||||
|
/// The state associated with the test execution of one of the tests.
|
||||||
|
pub struct ExecutionState {
|
||||||
|
/// The compiled contracts, these contracts have been compiled and have had the libraries linked
|
||||||
|
/// against them and therefore they're ready to be deployed on-demand.
|
||||||
|
pub compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||||
|
|
||||||
|
/// A map of all of the deployed contracts and information about them.
|
||||||
|
pub deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
|
||||||
|
|
||||||
|
/// This map stores the variables used for each one of the cases contained in the metadata file.
|
||||||
|
variables: HashMap<String, U256>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecutionState {
|
||||||
|
pub fn new(
|
||||||
|
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||||
|
deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
compiled_contracts,
|
||||||
|
deployed_contracts,
|
||||||
|
variables: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
//! This module contains all of the code responsible for performing differential tests including the
|
||||||
|
//! driver implementation, state implementation, and the core logic that allows for tests to be
|
||||||
|
//! executed.
|
||||||
|
|
||||||
|
mod driver;
|
||||||
|
mod entry_point;
|
||||||
|
mod execution_state;
|
||||||
|
|
||||||
|
pub use driver::*;
|
||||||
|
pub use entry_point::*;
|
||||||
|
pub use execution_state::*;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
use revive_dt_config::CorpusConfiguration;
|
||||||
|
use revive_dt_format::{corpus::Corpus, metadata::MetadataFile};
|
||||||
|
use tracing::{info, info_span, instrument};
|
||||||
|
|
||||||
|
/// Given an object that implements [`AsRef<CorpusConfiguration>`], this function finds all of the
|
||||||
|
/// corpus files and produces a map containing all of the [`MetadataFile`]s discovered.
|
||||||
|
#[instrument(level = "debug", name = "Collecting Corpora", skip_all)]
|
||||||
|
pub fn collect_metadata_files(
|
||||||
|
context: impl AsRef<CorpusConfiguration>,
|
||||||
|
) -> anyhow::Result<Vec<MetadataFile>> {
|
||||||
|
let mut metadata_files = Vec::new();
|
||||||
|
|
||||||
|
let corpus_configuration = AsRef::<CorpusConfiguration>::as_ref(&context);
|
||||||
|
for path in &corpus_configuration.paths {
|
||||||
|
let span = info_span!("Processing corpus file", path = %path.display());
|
||||||
|
let _guard = span.enter();
|
||||||
|
|
||||||
|
let corpus = Corpus::try_from_path(path)?;
|
||||||
|
info!(
|
||||||
|
name = corpus.name(),
|
||||||
|
number_of_contained_paths = corpus.path_count(),
|
||||||
|
"Deserialized corpus file"
|
||||||
|
);
|
||||||
|
metadata_files.extend(corpus.enumerate_tests());
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's a possibility that there are certain paths that all lead to the same metadata files
|
||||||
|
// and therefore it's important that we sort them and then deduplicate them.
|
||||||
|
metadata_files.sort_by(|a, b| a.metadata_file_path.cmp(&b.metadata_file_path));
|
||||||
|
metadata_files.dedup_by(|a, b| a.metadata_file_path == b.metadata_file_path);
|
||||||
|
|
||||||
|
Ok(metadata_files)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
mod cached_compiler;
|
||||||
|
mod metadata;
|
||||||
|
mod pool;
|
||||||
|
mod test;
|
||||||
|
|
||||||
|
pub use cached_compiler::*;
|
||||||
|
pub use metadata::*;
|
||||||
|
pub use pool::*;
|
||||||
|
pub use test::*;
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{borrow::Cow, path::Path};
|
||||||
|
|
||||||
|
use futures::{Stream, StreamExt, stream};
|
||||||
|
use indexmap::{IndexMap, indexmap};
|
||||||
|
use revive_dt_common::iterators::EitherIter;
|
||||||
|
use revive_dt_common::types::PlatformIdentifier;
|
||||||
|
use revive_dt_config::Context;
|
||||||
|
use revive_dt_format::mode::ParsedMode;
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
|
use revive_dt_compiler::Mode;
|
||||||
|
use revive_dt_compiler::SolidityCompiler;
|
||||||
|
use revive_dt_format::{
|
||||||
|
case::{Case, CaseIdx},
|
||||||
|
metadata::MetadataFile,
|
||||||
|
};
|
||||||
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
|
use revive_dt_report::{ExecutionSpecificReporter, Reporter};
|
||||||
|
use revive_dt_report::{TestSpecificReporter, TestSpecifier};
|
||||||
|
use tracing::{debug, error};
|
||||||
|
|
||||||
|
use crate::Platform;
|
||||||
|
use crate::helpers::NodePool;
|
||||||
|
|
||||||
|
async fn create_test_definitions_stream<'a>(
|
||||||
|
// This is only required for creating the compiler objects and is not used anywhere else in the
|
||||||
|
// function.
|
||||||
|
context: &Context,
|
||||||
|
metadata_files: impl IntoIterator<Item = &'a MetadataFile>,
|
||||||
|
platforms_and_nodes: &'a BTreeMap<PlatformIdentifier, (&dyn Platform, NodePool)>,
|
||||||
|
reporter: Reporter,
|
||||||
|
) -> impl Stream<Item = TestDefinition<'a>> {
|
||||||
|
stream::iter(
|
||||||
|
metadata_files
|
||||||
|
.into_iter()
|
||||||
|
// Flatten over the cases.
|
||||||
|
.flat_map(|metadata_file| {
|
||||||
|
metadata_file
|
||||||
|
.cases
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(move |(case_idx, case)| (metadata_file, case_idx, case))
|
||||||
|
})
|
||||||
|
// Flatten over the modes, prefer the case modes over the metadata file modes.
|
||||||
|
.flat_map(move |(metadata_file, case_idx, case)| {
|
||||||
|
let reporter = reporter.clone();
|
||||||
|
|
||||||
|
let modes = case.modes.as_ref().or(metadata_file.modes.as_ref());
|
||||||
|
let modes = match modes {
|
||||||
|
Some(modes) => EitherIter::A(
|
||||||
|
ParsedMode::many_to_modes(modes.iter()).map(Cow::<'static, _>::Owned),
|
||||||
|
),
|
||||||
|
None => EitherIter::B(Mode::all().map(Cow::<'static, _>::Borrowed)),
|
||||||
|
};
|
||||||
|
|
||||||
|
modes.into_iter().map(move |mode| {
|
||||||
|
(
|
||||||
|
metadata_file,
|
||||||
|
case_idx,
|
||||||
|
case,
|
||||||
|
mode.clone(),
|
||||||
|
reporter.test_specific_reporter(Arc::new(TestSpecifier {
|
||||||
|
solc_mode: mode.as_ref().clone(),
|
||||||
|
metadata_file_path: metadata_file.metadata_file_path.clone(),
|
||||||
|
case_idx: CaseIdx::new(case_idx),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Inform the reporter of each one of the test cases that were discovered which we expect to
|
||||||
|
// run.
|
||||||
|
.inspect(|(_, _, _, _, reporter)| {
|
||||||
|
reporter
|
||||||
|
.report_test_case_discovery_event()
|
||||||
|
.expect("Can't fail");
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
// Creating the Test Definition objects from all of the various objects we have and creating
|
||||||
|
// their required dependencies (e.g., compiler).
|
||||||
|
.filter_map(
|
||||||
|
move |(metadata_file, case_idx, case, mode, reporter)| async move {
|
||||||
|
let mut platforms = BTreeMap::new();
|
||||||
|
for (platform, node_pool) in platforms_and_nodes.values() {
|
||||||
|
let node = node_pool.round_robbin();
|
||||||
|
let compiler = platform
|
||||||
|
.new_compiler(context.clone(), mode.version.clone().map(Into::into))
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
error!(
|
||||||
|
?err,
|
||||||
|
platform_identifier = %platform.platform_identifier(),
|
||||||
|
"Failed to instantiate the compiler"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
reporter
|
||||||
|
.report_node_assigned_event(
|
||||||
|
node.id(),
|
||||||
|
platform.platform_identifier(),
|
||||||
|
node.connection_string(),
|
||||||
|
)
|
||||||
|
.expect("Can't fail");
|
||||||
|
|
||||||
|
let reporter =
|
||||||
|
reporter.execution_specific_reporter(node.id(), platform.platform_identifier());
|
||||||
|
|
||||||
|
platforms.insert(
|
||||||
|
platform.platform_identifier(),
|
||||||
|
TestPlatformInformation {
|
||||||
|
platform: *platform,
|
||||||
|
node,
|
||||||
|
compiler,
|
||||||
|
reporter,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(TestDefinition {
|
||||||
|
/* Metadata file information */
|
||||||
|
metadata: metadata_file,
|
||||||
|
metadata_file_path: metadata_file.metadata_file_path.as_path(),
|
||||||
|
|
||||||
|
/* Mode Information */
|
||||||
|
mode: mode.clone(),
|
||||||
|
|
||||||
|
/* Case Information */
|
||||||
|
case_idx: CaseIdx::new(case_idx),
|
||||||
|
case,
|
||||||
|
|
||||||
|
/* Platform and Node Assignment Information */
|
||||||
|
platforms,
|
||||||
|
|
||||||
|
/* Reporter */
|
||||||
|
reporter,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// Filter out the test cases which are incompatible or that can't run in the current setup.
|
||||||
|
.filter_map(move |test| async move {
|
||||||
|
match test.check_compatibility() {
|
||||||
|
Ok(()) => Some(test),
|
||||||
|
Err((reason, additional_information)) => {
|
||||||
|
debug!(
|
||||||
|
metadata_file_path = %test.metadata.metadata_file_path.display(),
|
||||||
|
case_idx = %test.case_idx,
|
||||||
|
mode = %test.mode,
|
||||||
|
reason,
|
||||||
|
additional_information =
|
||||||
|
serde_json::to_string(&additional_information).unwrap(),
|
||||||
|
"Ignoring Test Case"
|
||||||
|
);
|
||||||
|
test.reporter
|
||||||
|
.report_test_ignored_event(
|
||||||
|
reason.to_string(),
|
||||||
|
additional_information
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k.into(), v))
|
||||||
|
.collect::<IndexMap<_, _>>(),
|
||||||
|
)
|
||||||
|
.expect("Can't fail");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a full description of a differential test to run alongside the full metadata file, the
|
||||||
|
/// specific case to be tested, the platforms that the tests should run on, the specific nodes of
|
||||||
|
/// these platforms that they should run on, the compilers to use, and everything else needed making
|
||||||
|
/// it a complete description.
|
||||||
|
pub struct TestDefinition<'a> {
|
||||||
|
/* Metadata file information */
|
||||||
|
pub metadata: &'a MetadataFile,
|
||||||
|
pub metadata_file_path: &'a Path,
|
||||||
|
|
||||||
|
/* Mode Information */
|
||||||
|
pub mode: Cow<'a, Mode>,
|
||||||
|
|
||||||
|
/* Case Information */
|
||||||
|
pub case_idx: CaseIdx,
|
||||||
|
pub case: &'a Case,
|
||||||
|
|
||||||
|
/* Platform and Node Assignment Information */
|
||||||
|
pub platforms: BTreeMap<PlatformIdentifier, TestPlatformInformation<'a>>,
|
||||||
|
|
||||||
|
/* Reporter */
|
||||||
|
pub reporter: TestSpecificReporter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TestDefinition<'a> {
|
||||||
|
/// Checks if this test can be ran with the current configuration.
|
||||||
|
pub fn check_compatibility(&self) -> TestCheckFunctionResult {
|
||||||
|
self.check_metadata_file_ignored()?;
|
||||||
|
self.check_case_file_ignored()?;
|
||||||
|
self.check_target_compatibility()?;
|
||||||
|
self.check_evm_version_compatibility()?;
|
||||||
|
self.check_compiler_compatibility()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the metadata file is ignored or not.
|
||||||
|
fn check_metadata_file_ignored(&self) -> TestCheckFunctionResult {
|
||||||
|
if self.metadata.ignore.is_some_and(|ignore| ignore) {
|
||||||
|
Err(("Metadata file is ignored.", indexmap! {}))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the case file is ignored or not.
|
||||||
|
fn check_case_file_ignored(&self) -> TestCheckFunctionResult {
|
||||||
|
if self.case.ignore.is_some_and(|ignore| ignore) {
|
||||||
|
Err(("Case is ignored.", indexmap! {}))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the platforms all support the desired targets in the metadata file.
|
||||||
|
fn check_target_compatibility(&self) -> TestCheckFunctionResult {
|
||||||
|
let mut error_map = indexmap! {
|
||||||
|
"test_desired_targets" => json!(self.metadata.targets.as_ref()),
|
||||||
|
};
|
||||||
|
let mut is_allowed = true;
|
||||||
|
for (_, platform_information) in self.platforms.iter() {
|
||||||
|
let is_allowed_for_platform = match self.metadata.targets.as_ref() {
|
||||||
|
None => true,
|
||||||
|
Some(targets) => {
|
||||||
|
let mut target_matches = false;
|
||||||
|
for target in targets.iter() {
|
||||||
|
if &platform_information.platform.vm_identifier() == target {
|
||||||
|
target_matches = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target_matches
|
||||||
|
}
|
||||||
|
};
|
||||||
|
is_allowed &= is_allowed_for_platform;
|
||||||
|
error_map.insert(
|
||||||
|
platform_information.platform.platform_identifier().into(),
|
||||||
|
json!(is_allowed_for_platform),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_allowed {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err((
|
||||||
|
"One of the platforms do do not support the targets allowed by the test.",
|
||||||
|
error_map,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for the compatibility of the EVM version with the platforms specified.
|
||||||
|
fn check_evm_version_compatibility(&self) -> TestCheckFunctionResult {
|
||||||
|
let Some(evm_version_requirement) = self.metadata.required_evm_version else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut error_map = indexmap! {
|
||||||
|
"test_desired_evm_version" => json!(self.metadata.required_evm_version),
|
||||||
|
};
|
||||||
|
let mut is_allowed = true;
|
||||||
|
for (_, platform_information) in self.platforms.iter() {
|
||||||
|
let is_allowed_for_platform =
|
||||||
|
evm_version_requirement.matches(&platform_information.node.evm_version());
|
||||||
|
is_allowed &= is_allowed_for_platform;
|
||||||
|
error_map.insert(
|
||||||
|
platform_information.platform.platform_identifier().into(),
|
||||||
|
json!(is_allowed_for_platform),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_allowed {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err((
|
||||||
|
"EVM version is incompatible for the platforms specified",
|
||||||
|
error_map,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the platforms compilers support the mode that the test is for.
|
||||||
|
fn check_compiler_compatibility(&self) -> TestCheckFunctionResult {
|
||||||
|
let mut error_map = indexmap! {
|
||||||
|
"test_desired_evm_version" => json!(self.metadata.required_evm_version),
|
||||||
|
};
|
||||||
|
let mut is_allowed = true;
|
||||||
|
for (_, platform_information) in self.platforms.iter() {
|
||||||
|
let is_allowed_for_platform = platform_information
|
||||||
|
.compiler
|
||||||
|
.supports_mode(self.mode.optimize_setting, self.mode.pipeline);
|
||||||
|
is_allowed &= is_allowed_for_platform;
|
||||||
|
error_map.insert(
|
||||||
|
platform_information.platform.platform_identifier().into(),
|
||||||
|
json!(is_allowed_for_platform),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_allowed {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err((
|
||||||
|
"Compilers do not support this mode either for the provided platforms.",
|
||||||
|
error_map,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TestPlatformInformation<'a> {
|
||||||
|
pub platform: &'a dyn Platform,
|
||||||
|
pub node: &'a dyn EthereumNode,
|
||||||
|
pub compiler: Box<dyn SolidityCompiler>,
|
||||||
|
pub reporter: ExecutionSpecificReporter,
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestCheckFunctionResult = Result<(), (&'static str, IndexMap<&'static str, Value>)>;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
mod cached_compiler;
|
mod differential_tests;
|
||||||
mod pool;
|
mod helpers;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@@ -48,8 +48,7 @@ use revive_dt_format::{
|
|||||||
steps::{FunctionCallStep, Step},
|
steps::{FunctionCallStep, Step},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::cached_compiler::CachedCompiler;
|
use crate::helpers::*;
|
||||||
use crate::pool::NodePool;
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let (writer, _guard) = tracing_appender::non_blocking::NonBlockingBuilder::default()
|
let (writer, _guard) = tracing_appender::non_blocking::NonBlockingBuilder::default()
|
||||||
@@ -120,7 +119,7 @@ fn collect_corpora(
|
|||||||
) -> anyhow::Result<HashMap<Corpus, Vec<MetadataFile>>> {
|
) -> anyhow::Result<HashMap<Corpus, Vec<MetadataFile>>> {
|
||||||
let mut corpora = HashMap::new();
|
let mut corpora = HashMap::new();
|
||||||
|
|
||||||
for path in &context.corpus {
|
for path in &context.corpus_configuration.paths {
|
||||||
let span = info_span!("Processing corpus file", path = %path.display());
|
let span = info_span!("Processing corpus file", path = %path.display());
|
||||||
let _guard = span.enter();
|
let _guard = span.enter();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user