Implement basic reporting facility (#18)

* wip

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>

* save to file after all tasks done

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>

* error out early if the workdir does not exist

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>

* the compiler statistics

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>

* allow compiler statistics per implementation

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>

* save compiler problems

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>

* add flag whether to extract compiler errors

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>

* whitespace

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
This commit is contained in:
xermicus
2025-05-23 19:15:04 +02:00
committed by GitHub
parent 399f7820cd
commit 10bfaed461
18 changed files with 528 additions and 60 deletions
+47 -17
View File
@@ -8,6 +8,7 @@ use revive_dt_compiler::{Compiler, CompilerInput, SolidityCompiler};
use revive_dt_config::Arguments;
use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode};
use revive_dt_node_interaction::EthereumNode;
use revive_dt_report::reporter::{CompilationTask, Report, Span};
use revive_solc_json_interface::SolcStandardJsonOutput;
use crate::Platform;
@@ -19,6 +20,7 @@ type Contracts<T> = HashMap<
pub struct State<'a, T: Platform> {
config: &'a Arguments,
span: Span,
contracts: Contracts<T>,
deployed_contracts: HashMap<String, Address>,
}
@@ -27,37 +29,65 @@ impl<'a, T> State<'a, T>
where
T: Platform,
{
pub fn new(config: &'a Arguments) -> Self {
pub fn new(config: &'a Arguments, span: Span) -> Self {
Self {
config,
span,
contracts: Default::default(),
deployed_contracts: Default::default(),
}
}
/// Returns a copy of the current span.
fn span(&self) -> Span {
self.span
}
pub fn build_contracts(&mut self, mode: &SolcMode, metadata: &Metadata) -> anyhow::Result<()> {
let mut span = self.span();
span.next_metadata(
metadata
.file_path
.as_ref()
.expect("metadata should have been read from a file")
.clone(),
);
let Some(version) = mode.last_patch_version(&self.config.solc) else {
anyhow::bail!("unsupported solc version: {:?}", mode.solc_version);
anyhow::bail!("unsupported solc version: {:?}", &mode.solc_version);
};
let sources = metadata.contract_sources()?;
let base_path = metadata.directory()?.display().to_string();
let mut compiler = Compiler::<T::Compiler>::new()
.base_path(metadata.directory()?.display().to_string())
.solc_optimizer(mode.solc_optimize());
let mut compiler = Compiler::<T::Compiler>::new().base_path(base_path.clone());
for (file, _contract) in sources.values() {
for (file, _contract) in metadata.contract_sources()?.values() {
log::debug!("contract source {}", file.display());
compiler = compiler.with_source(file)?;
}
let mut task = CompilationTask {
json_input: compiler.input(),
json_output: None,
mode: mode.clone(),
compiler_version: format!("{}", &version),
error: None,
};
let compiler_path = T::Compiler::get_compiler_executable(self.config, version)?;
let output = compiler
.solc_optimizer(mode.solc_optimize())
.try_build(compiler_path)?;
self.contracts.insert(output.input, output.output);
Ok(())
match compiler.try_build(compiler_path) {
Ok(output) => {
task.json_output = Some(output.output.clone());
task.error = output.error;
self.contracts.insert(output.input, output.output);
Report::compilation(span, T::config_id(), task);
Ok(())
}
Err(error) => {
task.error = Some(error.to_string());
Err(error)
}
}
}
pub fn execute_input(
@@ -102,12 +132,12 @@ where
}
}
pub fn execute(&mut self) -> anyhow::Result<()> {
pub fn execute(&mut self, span: Span) -> anyhow::Result<()> {
for mode in self.metadata.solc_modes() {
let mut leader_state = State::<L>::new(self.config);
let mut leader_state = State::<L>::new(self.config, span);
leader_state.build_contracts(&mode, self.metadata)?;
let mut follower_state = State::<F>::new(self.config);
let mut follower_state = State::<F>::new(self.config, span);
follower_state.build_contracts(&mode, self.metadata)?;
for case in &self.metadata.cases {
+12
View File
@@ -4,6 +4,7 @@
//! provides a helper utilty to execute tests.
use revive_dt_compiler::{SolidityCompiler, revive_resolc, solc};
use revive_dt_config::TestingPlatform;
use revive_dt_node::geth;
use revive_dt_node_interaction::EthereumNode;
@@ -15,6 +16,9 @@ pub mod driver;
pub trait Platform {
type Blockchain: EthereumNode;
type Compiler: SolidityCompiler;
/// Returns the matching [TestingPlatform] of the [revive_dt_config::Arguments].
fn config_id() -> TestingPlatform;
}
#[derive(Default)]
@@ -23,6 +27,10 @@ pub struct Geth;
impl Platform for Geth {
type Blockchain = geth::Instance;
type Compiler = solc::Solc;
fn config_id() -> TestingPlatform {
TestingPlatform::Geth
}
}
#[derive(Default)]
@@ -31,4 +39,8 @@ pub struct Kitchensink;
impl Platform for Kitchensink {
type Blockchain = geth::Instance;
type Compiler = revive_resolc::Resolc;
fn config_id() -> TestingPlatform {
TestingPlatform::Kitchensink
}
}
+25 -24
View File
@@ -10,6 +10,7 @@ use revive_dt_core::{
};
use revive_dt_format::{corpus::Corpus, metadata::Metadata};
use revive_dt_node::pool::NodePool;
use revive_dt_report::reporter::{Report, Span};
use temp_dir::TempDir;
static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
@@ -17,18 +18,15 @@ static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
fn main() -> anyhow::Result<()> {
let args = init_cli()?;
let corpora = collect_corpora(&args)?;
for (corpus, tests) in collect_corpora(&args)? {
let span = Span::new(corpus, args.clone())?;
if let Some(platform) = &args.compile_only {
for tests in corpora.values() {
main_compile_only(&args, tests, platform)?;
match &args.compile_only {
Some(platform) => compile_corpus(&args, &tests, platform, span),
None => execute_corpus(&args, &tests, span)?,
}
return Ok(());
}
for tests in corpora.values() {
main_execute_differential(&args, tests)?;
Report::save()?;
}
Ok(())
@@ -38,17 +36,26 @@ fn init_cli() -> anyhow::Result<Arguments> {
env_logger::init();
let mut args = Arguments::parse();
if args.corpus.is_empty() {
anyhow::bail!("no test corpus specified");
}
if args.working_directory.is_none() {
args.temp_dir = Some(&TEMP_DIR);
match args.working_directory.as_ref() {
Some(dir) => {
if !dir.exists() {
anyhow::bail!("workdir {} does not exist", dir.display());
}
}
None => {
args.temp_dir = Some(&TEMP_DIR);
}
}
log::info!("workdir: {}", args.directory().display());
ThreadPoolBuilder::new()
.num_threads(args.workers)
.build_global()
.unwrap();
.build_global()?;
Ok(args)
}
@@ -67,7 +74,7 @@ fn collect_corpora(args: &Arguments) -> anyhow::Result<HashMap<Corpus, Vec<Metad
Ok(corpora)
}
fn main_execute_differential(args: &Arguments, tests: &[Metadata]) -> anyhow::Result<()> {
fn execute_corpus(args: &Arguments, tests: &[Metadata], span: Span) -> anyhow::Result<()> {
let leader_nodes = NodePool::new(args)?;
let follower_nodes = NodePool::new(args)?;
@@ -82,7 +89,7 @@ fn main_execute_differential(args: &Arguments, tests: &[Metadata]) -> anyhow::Re
_ => unimplemented!(),
};
match driver.execute() {
match driver.execute(span) {
Ok(build) => {
log::info!(
"metadata {} success",
@@ -102,25 +109,19 @@ fn main_execute_differential(args: &Arguments, tests: &[Metadata]) -> anyhow::Re
Ok(())
}
fn main_compile_only(
config: &Arguments,
tests: &[Metadata],
platform: &TestingPlatform,
) -> anyhow::Result<()> {
fn compile_corpus(config: &Arguments, tests: &[Metadata], platform: &TestingPlatform, span: Span) {
tests.par_iter().for_each(|metadata| {
for mode in &metadata.solc_modes() {
match platform {
TestingPlatform::Geth => {
let mut state = State::<Geth>::new(config);
let mut state = State::<Geth>::new(config, span);
let _ = state.build_contracts(mode, metadata);
}
TestingPlatform::Kitchensink => {
let mut state = State::<Kitchensink>::new(config);
let mut state = State::<Kitchensink>::new(config, span);
let _ = state.build_contracts(mode, metadata);
}
};
}
});
Ok(())
}