mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-06-12 06:31:14 +00:00
Better logging and fix concurrency issues
This commit is contained in:
@@ -25,3 +25,6 @@ serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+54
-55
@@ -3,10 +3,11 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use revive_dt_common::cached_fs::read_dir;
|
||||
use revive_dt_common::iterators::FilesWithExtensionIterator;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::metadata::MetadataFile;
|
||||
use crate::metadata::{Metadata, MetadataFile};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
@@ -18,7 +19,7 @@ pub enum Corpus {
|
||||
impl Corpus {
|
||||
pub fn try_from_path(file_path: impl AsRef<Path>) -> anyhow::Result<Self> {
|
||||
let mut corpus = File::open(file_path.as_ref())
|
||||
.map_err(Into::<anyhow::Error>::into)
|
||||
.map_err(anyhow::Error::from)
|
||||
.and_then(|file| serde_json::from_reader::<_, Corpus>(file).map_err(Into::into))?;
|
||||
|
||||
for path in corpus.paths_iter_mut() {
|
||||
@@ -42,10 +43,52 @@ impl Corpus {
|
||||
}
|
||||
|
||||
pub fn enumerate_tests(&self) -> Vec<MetadataFile> {
|
||||
let mut tests = Vec::new();
|
||||
for path in self.paths_iter() {
|
||||
collect_metadata(path, &mut tests);
|
||||
}
|
||||
let mut tests = self
|
||||
.paths_iter()
|
||||
.flat_map(|root_path| {
|
||||
if !root_path.is_dir() {
|
||||
Box::new(std::iter::once(root_path.to_path_buf()))
|
||||
as Box<dyn Iterator<Item = _>>
|
||||
} else {
|
||||
Box::new(
|
||||
FilesWithExtensionIterator::new(root_path)
|
||||
.with_use_cached_fs(true)
|
||||
.with_allowed_extension("sol")
|
||||
.with_allowed_extension("json"),
|
||||
)
|
||||
}
|
||||
.map(move |metadata_file_path| (root_path, metadata_file_path))
|
||||
})
|
||||
.filter_map(|(root_path, metadata_file_path)| {
|
||||
Metadata::try_from_file(&metadata_file_path)
|
||||
.or_else(|| {
|
||||
debug!(
|
||||
discovered_from = %root_path.display(),
|
||||
metadata_file_path = %metadata_file_path.display(),
|
||||
"Skipping file since it doesn't contain valid metadata"
|
||||
);
|
||||
None
|
||||
})
|
||||
.map(|metadata| MetadataFile {
|
||||
metadata_file_path,
|
||||
corpus_file_path: root_path.to_path_buf(),
|
||||
content: metadata,
|
||||
})
|
||||
.inspect(|metadata_file| {
|
||||
debug!(
|
||||
metadata_file_path = %metadata_file.relative_path().display(),
|
||||
"Loaded metadata file"
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
tests.sort_by(|a, b| a.metadata_file_path.cmp(&b.metadata_file_path));
|
||||
tests.dedup_by(|a, b| a.metadata_file_path == b.metadata_file_path);
|
||||
info!(
|
||||
len = tests.len(),
|
||||
corpus_name = self.name(),
|
||||
"Found tests in Corpus"
|
||||
);
|
||||
tests
|
||||
}
|
||||
|
||||
@@ -76,55 +119,11 @@ impl Corpus {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursively walks `path` and parses any JSON or Solidity file into a test
|
||||
/// definition [Metadata].
|
||||
///
|
||||
/// Found tests are inserted into `tests`.
|
||||
///
|
||||
/// `path` is expected to be a directory.
|
||||
pub fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
|
||||
if path.is_dir() {
|
||||
let dir_entry = match read_dir(path) {
|
||||
Ok(dir_entry) => dir_entry,
|
||||
Err(error) => {
|
||||
tracing::error!("failed to read dir '{}': {error}", path.display());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for path in dir_entry {
|
||||
let path = match path {
|
||||
Ok(entry) => entry,
|
||||
Err(error) => {
|
||||
tracing::error!("error reading dir entry: {error}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if path.is_dir() {
|
||||
collect_metadata(&path, tests);
|
||||
continue;
|
||||
}
|
||||
|
||||
if path.is_file() {
|
||||
if let Some(metadata) = MetadataFile::try_from_file(&path) {
|
||||
tests.push(metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let Some(extension) = path.extension() else {
|
||||
tracing::error!("Failed to get file extension");
|
||||
return;
|
||||
};
|
||||
if extension.eq_ignore_ascii_case("sol") || extension.eq_ignore_ascii_case("json") {
|
||||
if let Some(metadata) = MetadataFile::try_from_file(path) {
|
||||
tests.push(metadata)
|
||||
}
|
||||
} else {
|
||||
tracing::error!(?extension, "Unsupported file extension");
|
||||
pub fn path_count(&self) -> usize {
|
||||
match self {
|
||||
Corpus::SinglePath { .. } => 1,
|
||||
Corpus::MultiplePaths { paths, .. } => paths.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-31
@@ -2,7 +2,6 @@ use std::collections::HashMap;
|
||||
|
||||
use alloy::{
|
||||
eips::BlockNumberOrTag,
|
||||
hex::ToHexExt,
|
||||
json_abi::Function,
|
||||
network::TransactionBuilder,
|
||||
primitives::{Address, Bytes, U256},
|
||||
@@ -14,6 +13,7 @@ use semver::VersionReq;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use revive_dt_common::macros::define_wrapper_type;
|
||||
use tracing::{Instrument, info_span, instrument};
|
||||
|
||||
use crate::traits::ResolverApi;
|
||||
use crate::{metadata::ContractInstance, traits::ResolutionContext};
|
||||
@@ -33,6 +33,11 @@ pub enum Step {
|
||||
StorageEmptyAssertion(Box<StorageEmptyAssertion>),
|
||||
}
|
||||
|
||||
define_wrapper_type!(
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct StepIdx(usize) impl Display;
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct Input {
|
||||
#[serde(default = "Input::default_caller")]
|
||||
@@ -268,15 +273,9 @@ impl Input {
|
||||
}
|
||||
Method::FunctionName(ref function_name) => {
|
||||
let Some(abi) = context.deployed_contract_abi(&self.instance) else {
|
||||
tracing::error!(
|
||||
contract_name = self.instance.as_ref(),
|
||||
"Attempted to lookup ABI of contract but it wasn't found"
|
||||
);
|
||||
anyhow::bail!("ABI for instance '{}' not found", self.instance.as_ref());
|
||||
};
|
||||
|
||||
tracing::trace!("ABI found for instance: {}", &self.instance.as_ref());
|
||||
|
||||
// We follow the same logic that's implemented in the matter-labs-tester where they resolve
|
||||
// the function name into a function selector and they assume that he function doesn't have
|
||||
// any existing overloads.
|
||||
@@ -302,13 +301,6 @@ impl Input {
|
||||
.selector()
|
||||
};
|
||||
|
||||
tracing::trace!("Functions found for instance: {}", self.instance.as_ref());
|
||||
|
||||
tracing::trace!(
|
||||
"Starting encoding ABI's parameters for instance: {}",
|
||||
self.instance.as_ref()
|
||||
);
|
||||
|
||||
// Allocating a vector that we will be using for the calldata. The vector size will be:
|
||||
// 4 bytes for the function selector.
|
||||
// function.inputs.len() * 32 bytes for the arguments (each argument is a U256).
|
||||
@@ -436,15 +428,12 @@ impl Calldata {
|
||||
}
|
||||
Calldata::Compound(items) => {
|
||||
for (arg_idx, arg) in items.iter().enumerate() {
|
||||
match arg.resolve(resolver, context).await {
|
||||
Ok(resolved) => {
|
||||
buffer.extend(resolved.to_be_bytes::<32>());
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(?arg, arg_idx, ?error, "Failed to resolve argument");
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
buffer.extend(
|
||||
arg.resolve(resolver, context)
|
||||
.instrument(info_span!("Resolving argument", %arg, arg_idx))
|
||||
.await?
|
||||
.to_be_bytes::<32>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -498,6 +487,7 @@ impl Calldata {
|
||||
}
|
||||
|
||||
impl CalldataItem {
|
||||
#[instrument(level = "info", skip_all, err)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
resolver: &impl ResolverApi,
|
||||
@@ -548,14 +538,7 @@ impl CalldataItem {
|
||||
match stack.as_slice() {
|
||||
// Empty stack means that we got an empty compound calldata which we resolve to zero.
|
||||
[] => Ok(U256::ZERO),
|
||||
[CalldataToken::Item(item)] => {
|
||||
tracing::debug!(
|
||||
original = self.0,
|
||||
resolved = item.to_be_bytes::<32>().encode_hex(),
|
||||
"Resolved a Calldata item"
|
||||
);
|
||||
Ok(*item)
|
||||
}
|
||||
[CalldataToken::Item(item)] => Ok(*item),
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"Invalid calldata arithmetic operation - Invalid stack"
|
||||
)),
|
||||
|
||||
@@ -15,6 +15,7 @@ use revive_dt_common::{
|
||||
cached_fs::read_to_string, iterators::FilesWithExtensionIterator, macros::define_wrapper_type,
|
||||
types::Mode,
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
use crate::{case::Case, mode::ParsedMode};
|
||||
|
||||
@@ -24,16 +25,26 @@ pub const SOLIDITY_CASE_COMMENT_MARKER: &str = "//!";
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
||||
pub struct MetadataFile {
|
||||
pub path: PathBuf,
|
||||
/// The path of the metadata file. This will either be a JSON or solidity file.
|
||||
pub metadata_file_path: PathBuf,
|
||||
|
||||
/// This is the path contained within the corpus file. This could either be the path of some dir
|
||||
/// or could be the actual metadata file path.
|
||||
pub corpus_file_path: PathBuf,
|
||||
|
||||
/// The metadata contained within the file.
|
||||
pub content: Metadata,
|
||||
}
|
||||
|
||||
impl MetadataFile {
|
||||
pub fn try_from_file(path: &Path) -> Option<Self> {
|
||||
Metadata::try_from_file(path).map(|metadata| Self {
|
||||
path: path.to_owned(),
|
||||
content: metadata,
|
||||
})
|
||||
pub fn relative_path(&self) -> &Path {
|
||||
if self.corpus_file_path.is_file() {
|
||||
&self.corpus_file_path
|
||||
} else {
|
||||
self.metadata_file_path
|
||||
.strip_prefix(&self.corpus_file_path)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,10 +156,7 @@ impl Metadata {
|
||||
pub fn try_from_file(path: &Path) -> Option<Self> {
|
||||
assert!(path.is_file(), "not a file: {}", path.display());
|
||||
|
||||
let Some(file_extension) = path.extension() else {
|
||||
tracing::debug!("skipping corpus file: {}", path.display());
|
||||
return None;
|
||||
};
|
||||
let file_extension = path.extension()?;
|
||||
|
||||
if file_extension == METADATA_FILE_EXTENSION {
|
||||
return Self::try_from_json(path);
|
||||
@@ -158,18 +166,12 @@ impl Metadata {
|
||||
return Self::try_from_solidity(path);
|
||||
}
|
||||
|
||||
tracing::debug!("ignoring invalid corpus file: {}", path.display());
|
||||
None
|
||||
}
|
||||
|
||||
fn try_from_json(path: &Path) -> Option<Self> {
|
||||
let file = File::open(path)
|
||||
.inspect_err(|error| {
|
||||
tracing::error!(
|
||||
"opening JSON test metadata file '{}' error: {error}",
|
||||
path.display()
|
||||
);
|
||||
})
|
||||
.inspect_err(|err| error!(path = %path.display(), %err, "Failed to open file"))
|
||||
.ok()?;
|
||||
|
||||
match serde_json::from_reader::<_, Metadata>(file) {
|
||||
@@ -177,11 +179,8 @@ impl Metadata {
|
||||
metadata.file_path = Some(path.to_path_buf());
|
||||
Some(metadata)
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"parsing JSON test metadata file '{}' error: {error}",
|
||||
path.display()
|
||||
);
|
||||
Err(err) => {
|
||||
error!(path = %path.display(), %err, "Deserialization of metadata failed");
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -189,12 +188,7 @@ impl Metadata {
|
||||
|
||||
fn try_from_solidity(path: &Path) -> Option<Self> {
|
||||
let spec = read_to_string(path)
|
||||
.inspect_err(|error| {
|
||||
tracing::error!(
|
||||
"opening JSON test metadata file '{}' error: {error}",
|
||||
path.display()
|
||||
);
|
||||
})
|
||||
.inspect_err(|err| error!(path = %path.display(), %err, "Failed to read file content"))
|
||||
.ok()?
|
||||
.lines()
|
||||
.filter_map(|line| line.strip_prefix(SOLIDITY_CASE_COMMENT_MARKER))
|
||||
@@ -222,11 +216,8 @@ impl Metadata {
|
||||
);
|
||||
Some(metadata)
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"parsing Solidity test metadata file '{}' error: '{error}' from data: {spec}",
|
||||
path.display()
|
||||
);
|
||||
Err(err) => {
|
||||
error!(path = %path.display(), %err, "Failed to deserialize metadata");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user