Compare commits

..

1 Commits

Author SHA1 Message Date
Omar Abdulla e98f42978d Add compiler directives to metadata 2025-08-13 17:50:11 +03:00
7 changed files with 41 additions and 83 deletions
@@ -1,38 +0,0 @@
//! Implements a cached file system that allows for files to be read once into memory and then when
//! they're requested to be read again they will be returned from the cache.
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::{Arc, LazyLock},
};
use anyhow::Result;
use tokio::sync::RwLock;
#[allow(clippy::type_complexity)]
static CACHE: LazyLock<Arc<RwLock<HashMap<PathBuf, Vec<u8>>>>> = LazyLock::new(Default::default);
pub struct CachedFileSystem;
impl CachedFileSystem {
pub async fn read(path: impl AsRef<Path>) -> Result<Vec<u8>> {
let cache_read_lock = CACHE.read().await;
match cache_read_lock.get(path.as_ref()) {
Some(entry) => Ok(entry.clone()),
None => {
drop(cache_read_lock);
let content = std::fs::read(&path)?;
let mut cache_write_lock = CACHE.write().await;
cache_write_lock.insert(path.as_ref().to_path_buf(), content.clone());
Ok(content)
}
}
}
pub async fn read_to_string(path: impl AsRef<Path>) -> Result<String> {
let content = Self::read(path).await?;
String::from_utf8(content).map_err(Into::into)
}
}
-2
View File
@@ -1,5 +1,3 @@
mod cached_file_system;
mod clear_dir; mod clear_dir;
pub use cached_file_system::*;
pub use clear_dir::*; pub use clear_dir::*;
+6 -6
View File
@@ -5,6 +5,7 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::read_to_string,
hash::Hash, hash::Hash,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@@ -15,7 +16,7 @@ use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use revive_common::EVMVersion; use revive_common::EVMVersion;
use revive_dt_common::{fs::CachedFileSystem, types::VersionOrRequirement}; use revive_dt_common::types::VersionOrRequirement;
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
pub mod revive_js; pub mod revive_js;
@@ -122,11 +123,10 @@ where
self self
} }
pub async fn with_source(mut self, path: impl AsRef<Path>) -> anyhow::Result<Self> { pub fn with_source(mut self, path: impl AsRef<Path>) -> anyhow::Result<Self> {
self.input.sources.insert( self.input
path.as_ref().to_path_buf(), .sources
CachedFileSystem::read_to_string(path.as_ref()).await?, .insert(path.as_ref().to_path_buf(), read_to_string(path.as_ref())?);
);
Ok(self) Ok(self)
} }
+11 -11
View File
@@ -67,7 +67,7 @@ fn main() -> anyhow::Result<()> {
let args = init_cli()?; let args = init_cli()?;
let body = async { let body = async {
for (corpus, tests) in collect_corpora(&args).await? { for (corpus, tests) in collect_corpora(&args)? {
let span = Span::new(corpus, args.clone())?; let span = Span::new(corpus, args.clone())?;
match &args.compile_only { match &args.compile_only {
Some(platform) => compile_corpus(&args, &tests, platform, span).await, Some(platform) => compile_corpus(&args, &tests, platform, span).await,
@@ -117,13 +117,13 @@ fn init_cli() -> anyhow::Result<Arguments> {
Ok(args) Ok(args)
} }
async fn collect_corpora(args: &Arguments) -> anyhow::Result<HashMap<Corpus, Vec<MetadataFile>>> { fn collect_corpora(args: &Arguments) -> anyhow::Result<HashMap<Corpus, Vec<MetadataFile>>> {
let mut corpora = HashMap::new(); let mut corpora = HashMap::new();
for path in &args.corpus { for path in &args.corpus {
let corpus = Corpus::try_from_path(path)?; let corpus = Corpus::try_from_path(path)?;
tracing::info!("found corpus: {}", path.display()); tracing::info!("found corpus: {}", path.display());
let tests = corpus.enumerate_tests().await; let tests = corpus.enumerate_tests();
tracing::info!("corpus '{}' contains {} tests", &corpus.name, tests.len()); tracing::info!("corpus '{}' contains {} tests", &corpus.name, tests.len());
corpora.insert(corpus, tests); corpora.insert(corpus, tests);
} }
@@ -145,7 +145,7 @@ where
let (report_tx, report_rx) = mpsc::unbounded_channel::<(Test, CaseResult)>(); let (report_tx, report_rx) = mpsc::unbounded_channel::<(Test, CaseResult)>();
let tests = prepare_tests::<L, F>(metadata_files); let tests = prepare_tests::<L, F>(metadata_files);
let driver_task = start_driver_task::<L, F>(args, tests, span, report_tx).await?; let driver_task = start_driver_task::<L, F>(args, tests, span, report_tx)?;
let status_reporter_task = start_reporter_task(report_rx); let status_reporter_task = start_reporter_task(report_rx);
tokio::join!(status_reporter_task, driver_task); tokio::join!(status_reporter_task, driver_task);
@@ -237,7 +237,7 @@ where
}) })
} }
async fn start_driver_task<L, F>( fn start_driver_task<L, F>(
args: &Arguments, args: &Arguments,
tests: impl Iterator<Item = Test>, tests: impl Iterator<Item = Test>,
span: Span, span: Span,
@@ -249,8 +249,8 @@ where
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static, L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static, F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
{ {
let leader_nodes = Arc::new(NodePool::<L::Blockchain>::new(args).await?); let leader_nodes = Arc::new(NodePool::<L::Blockchain>::new(args)?);
let follower_nodes = Arc::new(NodePool::<F::Blockchain>::new(args).await?); let follower_nodes = Arc::new(NodePool::<F::Blockchain>::new(args)?);
let compilation_cache = Arc::new(RwLock::new(HashMap::new())); let compilation_cache = Arc::new(RwLock::new(HashMap::new()));
let number_concurrent_tasks = args.number_of_concurrent_tasks(); let number_concurrent_tasks = args.number_of_concurrent_tasks();
@@ -693,12 +693,12 @@ async fn compile_contracts<P: Platform>(
"Compiling contracts" "Compiling contracts"
); );
let mut compiler = Compiler::<P::Compiler>::new() let compiler = Compiler::<P::Compiler>::new()
.with_allow_path(metadata.directory()?) .with_allow_path(metadata.directory()?)
.with_optimization(mode.solc_optimize()); .with_optimization(mode.solc_optimize());
for path in metadata.files_to_compile()? { let mut compiler = metadata
compiler = compiler.with_source(path).await?; .files_to_compile()?
} .try_fold(compiler, |compiler, path| compiler.with_source(&path))?;
for (library_instance, (library_address, _)) in deployed_libraries.iter() { for (library_instance, (library_address, _)) in deployed_libraries.iter() {
let library_ident = &metadata let library_ident = &metadata
.contracts .contracts
+6 -6
View File
@@ -39,9 +39,9 @@ impl Corpus {
} }
/// Scan the corpus base directory and return all tests found. /// Scan the corpus base directory and return all tests found.
pub async fn enumerate_tests(&self) -> Vec<MetadataFile> { pub fn enumerate_tests(&self) -> Vec<MetadataFile> {
let mut tests = Vec::new(); let mut tests = Vec::new();
collect_metadata(&self.path, &mut tests).await; collect_metadata(&self.path, &mut tests);
tests tests
} }
} }
@@ -52,7 +52,7 @@ impl Corpus {
/// Found tests are inserted into `tests`. /// Found tests are inserted into `tests`.
/// ///
/// `path` is expected to be a directory. /// `path` is expected to be a directory.
pub async fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) { pub fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
if path.is_dir() { if path.is_dir() {
let dir_entry = match std::fs::read_dir(path) { let dir_entry = match std::fs::read_dir(path) {
Ok(dir_entry) => dir_entry, Ok(dir_entry) => dir_entry,
@@ -73,12 +73,12 @@ pub async fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
let path = entry.path(); let path = entry.path();
if path.is_dir() { if path.is_dir() {
Box::pin(collect_metadata(&path, tests)).await; collect_metadata(&path, tests);
continue; continue;
} }
if path.is_file() { if path.is_file() {
if let Some(metadata) = MetadataFile::try_from_file(&path).await { if let Some(metadata) = MetadataFile::try_from_file(&path) {
tests.push(metadata) tests.push(metadata)
} }
} }
@@ -89,7 +89,7 @@ pub async fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
return; return;
}; };
if extension.eq_ignore_ascii_case("sol") || extension.eq_ignore_ascii_case("json") { if extension.eq_ignore_ascii_case("sol") || extension.eq_ignore_ascii_case("json") {
if let Some(metadata) = MetadataFile::try_from_file(path).await { if let Some(metadata) = MetadataFile::try_from_file(path) {
tests.push(metadata) tests.push(metadata)
} }
} else { } else {
+12 -15
View File
@@ -2,6 +2,7 @@ use std::{
cmp::Ordering, cmp::Ordering,
collections::BTreeMap, collections::BTreeMap,
fmt::Display, fmt::Display,
fs::{File, read_to_string},
ops::Deref, ops::Deref,
path::{Path, PathBuf}, path::{Path, PathBuf},
str::FromStr, str::FromStr,
@@ -10,9 +11,7 @@ use std::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use revive_common::EVMVersion; use revive_common::EVMVersion;
use revive_dt_common::{ use revive_dt_common::{iterators::FilesWithExtensionIterator, macros::define_wrapper_type};
fs::CachedFileSystem, iterators::FilesWithExtensionIterator, macros::define_wrapper_type,
};
use crate::{ use crate::{
case::Case, case::Case,
@@ -30,8 +29,8 @@ pub struct MetadataFile {
} }
impl MetadataFile { impl MetadataFile {
pub async fn try_from_file(path: &Path) -> Option<Self> { pub fn try_from_file(path: &Path) -> Option<Self> {
Metadata::try_from_file(path).await.map(|metadata| Self { Metadata::try_from_file(path).map(|metadata| Self {
path: path.to_owned(), path: path.to_owned(),
content: metadata, content: metadata,
}) })
@@ -152,7 +151,7 @@ impl Metadata {
/// ///
/// # Panics /// # Panics
/// Expects the supplied `path` to be a file. /// Expects the supplied `path` to be a file.
pub async fn try_from_file(path: &Path) -> Option<Self> { pub fn try_from_file(path: &Path) -> Option<Self> {
assert!(path.is_file(), "not a file: {}", path.display()); assert!(path.is_file(), "not a file: {}", path.display());
let Some(file_extension) = path.extension() else { let Some(file_extension) = path.extension() else {
@@ -161,20 +160,19 @@ impl Metadata {
}; };
if file_extension == METADATA_FILE_EXTENSION { if file_extension == METADATA_FILE_EXTENSION {
return Self::try_from_json(path).await; return Self::try_from_json(path);
} }
if file_extension == SOLIDITY_CASE_FILE_EXTENSION { if file_extension == SOLIDITY_CASE_FILE_EXTENSION {
return Self::try_from_solidity(path).await; return Self::try_from_solidity(path);
} }
tracing::debug!("ignoring invalid corpus file: {}", path.display()); tracing::debug!("ignoring invalid corpus file: {}", path.display());
None None
} }
async fn try_from_json(path: &Path) -> Option<Self> { fn try_from_json(path: &Path) -> Option<Self> {
let content = CachedFileSystem::read(path) let file = File::open(path)
.await
.inspect_err(|error| { .inspect_err(|error| {
tracing::error!( tracing::error!(
"opening JSON test metadata file '{}' error: {error}", "opening JSON test metadata file '{}' error: {error}",
@@ -183,7 +181,7 @@ impl Metadata {
}) })
.ok()?; .ok()?;
match serde_json::from_slice::<Metadata>(content.as_slice()) { match serde_json::from_reader::<_, Metadata>(file) {
Ok(mut metadata) => { Ok(mut metadata) => {
metadata.file_path = Some(path.to_path_buf()); metadata.file_path = Some(path.to_path_buf());
Some(metadata) Some(metadata)
@@ -198,9 +196,8 @@ impl Metadata {
} }
} }
async fn try_from_solidity(path: &Path) -> Option<Self> { fn try_from_solidity(path: &Path) -> Option<Self> {
let spec = CachedFileSystem::read_to_string(path) let spec = read_to_string(path)
.await
.inspect_err(|error| { .inspect_err(|error| {
tracing::error!( tracing::error!(
"opening JSON test metadata file '{}' error: {error}", "opening JSON test metadata file '{}' error: {error}",
+6 -5
View File
@@ -1,12 +1,12 @@
//! This crate implements concurrent handling of testing node. //! This crate implements concurrent handling of testing node.
use std::{ use std::{
fs::read_to_string,
sync::atomic::{AtomicUsize, Ordering}, sync::atomic::{AtomicUsize, Ordering},
thread, thread,
}; };
use anyhow::Context; use anyhow::Context;
use revive_dt_common::fs::CachedFileSystem;
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
use crate::Node; use crate::Node;
@@ -23,11 +23,12 @@ where
T: Node + Send + 'static, T: Node + Send + 'static,
{ {
/// Create a new Pool. This will start as many nodes as there are workers in `config`. /// Create a new Pool. This will start as many nodes as there are workers in `config`.
pub async fn new(config: &Arguments) -> anyhow::Result<Self> { pub fn new(config: &Arguments) -> anyhow::Result<Self> {
let nodes = config.number_of_nodes; let nodes = config.number_of_nodes;
let genesis = CachedFileSystem::read_to_string(&config.genesis_file) let genesis = read_to_string(&config.genesis_file).context(format!(
.await "can not read genesis file: {}",
.context("Failed to read genesis file")?; config.genesis_file.display()
))?;
let mut handles = Vec::with_capacity(nodes); let mut handles = Vec::with_capacity(nodes);
for _ in 0..nodes { for _ in 0..nodes {