From ac8051b03e76038161e6973397a272dcf023948a Mon Sep 17 00:00:00 2001 From: Omar Abdulla Date: Thu, 7 Aug 2025 15:36:10 +0300 Subject: [PATCH] refactor logic into a module --- Cargo.lock | 7 + Cargo.toml | 1 + crates/format/Cargo.toml | 1 + crates/format/src/semantic_tests/mod.rs | 10 + .../sections.rs} | 278 +++--------------- .../src/semantic_tests/test_configuration.rs | 200 +++++++++++++ 6 files changed, 261 insertions(+), 236 deletions(-) create mode 100644 crates/format/src/semantic_tests/mod.rs rename crates/format/src/{semantic_tests.rs => semantic_tests/sections.rs} (60%) create mode 100644 crates/format/src/semantic_tests/test_configuration.rs diff --git a/Cargo.lock b/Cargo.lock index 411c0ee..4be0063 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2929,6 +2929,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + [[package]] name = "integer-sqrt" version = "0.1.5" @@ -4095,6 +4101,7 @@ dependencies = [ "alloy-primitives", "alloy-sol-types", "anyhow", + "indoc", "revive-common", "revive-dt-common", "semver 1.0.26", diff --git a/Cargo.toml b/Cargo.toml index 92bb064..e8b3b3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ tracing-subscriber = { version = "0.3.19", default-features = false, features = "env-filter", ] } indexmap = { version = "2.10.0", default-features = false } +indoc = { version = "2.0.6", default-features = false } # revive compiler revive-solc-json-interface = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" } diff --git a/crates/format/Cargo.toml b/crates/format/Cargo.toml index 0494201..1fe1ffc 100644 --- a/crates/format/Cargo.toml +++ b/crates/format/Cargo.toml @@ -24,3 +24,4 @@ revive-common = { workspace = true } [dev-dependencies] tokio = { workspace = true } +indoc = { workspace = true } diff --git a/crates/format/src/semantic_tests/mod.rs b/crates/format/src/semantic_tests/mod.rs new file mode 100644 index 0000000..fd30fce --- /dev/null +++ b/crates/format/src/semantic_tests/mod.rs @@ -0,0 +1,10 @@ +//! This module contains a parser for the Solidity semantic tests allowing them to be parsed into +//! regular [`Metadata`] objects that can be executed by the testing framework. +//! +//! [`Metadata`]: crate::metadata::Metadata + +mod sections; +mod test_configuration; + +pub use sections::*; +pub use test_configuration::*; diff --git a/crates/format/src/semantic_tests.rs b/crates/format/src/semantic_tests/sections.rs similarity index 60% rename from crates/format/src/semantic_tests.rs rename to crates/format/src/semantic_tests/sections.rs index 5716614..57f031f 100644 --- a/crates/format/src/semantic_tests.rs +++ b/crates/format/src/semantic_tests/sections.rs @@ -1,17 +1,12 @@ -//! This module contains a parser for the Solidity semantic tests allowing them to be parsed into -//! regular [`Metadata`] objects that can be executed by the testing framework. -//! -//! [`Metadata`]: crate::metadata::Metadata +use std::{collections::VecDeque, path::PathBuf}; -use std::{collections::VecDeque, path::PathBuf, str::FromStr}; +use anyhow::{Context, Result, anyhow}; -use revive_common::EVMVersion; - -use anyhow::{Context, Error, Result, anyhow, bail}; +use crate::semantic_tests::TestConfiguration; /// This enum describes the various sections that a semantic test can contain. #[derive(Clone, Debug, PartialEq, Eq)] -enum SemanticTestSection { +pub enum SemanticTestSection { /// A source code section that consists of Solidity code. /// /// Source code sections might have a file name and they might not. Take the following section @@ -222,220 +217,29 @@ impl SemanticTestSection { } } -/// The configuration parameters provided in the solidity semantic tests. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct TestConfiguration { - /// Controls if the test case compiles through the Yul IR. - compile_via_yul: Option, - /// Controls if the compilation should be done to EWASM. - compile_to_ewasm: Option, - /// Controls if ABI encoding should be restricted to the V1 ABI encoder. - abi_encoder_v1_only: Option, - /// Controls the EVM Version that the test is compatible with. - evm_version: Option, - /// Controls how the revert strings should be handled. - revert_strings: Option, - /// Controls if non-existent functions should be permitted or not. - allow_non_existing_functions: Option, - /// The list of bytecode formats that this test should be run against. - bytecode_format: Option>, -} - -impl TestConfiguration { - pub fn new() -> Self { - Self::default() - } - - pub fn with_config( - &mut self, - key: impl AsRef, - value: impl AsRef, - ) -> Result<&mut Self> { - match key.as_ref() { - "compileViaYul" => self.compile_via_yul = Some(value.as_ref().parse()?), - "compileToEwasm" => self.compile_to_ewasm = Some(value.as_ref().parse()?), - "ABIEncoderV1Only" => self.abi_encoder_v1_only = Some(value.as_ref().parse()?), - "EVMVersion" => self.evm_version = Some(value.as_ref().parse()?), - "revertStrings" => self.revert_strings = Some(value.as_ref().parse()?), - "allowNonExistingFunctions" => { - self.allow_non_existing_functions = Some(value.as_ref().parse()?) - } - "bytecodeFormat" => { - self.bytecode_format = Some( - value - .as_ref() - .split(',') - .map(str::trim) - .map(FromStr::from_str) - .collect::>>()?, - ) - } - _ => bail!("Unknown test configuration {}", key.as_ref()), - }; - Ok(self) - } - - pub fn new_from_pairs( - pairs: impl IntoIterator, impl AsRef)>, - ) -> Result { - let mut this = Self::default(); - pairs - .into_iter() - .try_fold(&mut this, |this, (key, value)| this.with_config(key, value))?; - Ok(this) - } -} - -/// The configuration of a single item in the test configuration. -#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum ItemConfig { - /// The configuration is set to e a boolean that's either `true` or `false`. - Boolean(bool), - /// The `also` - Also, -} - -impl FromStr for ItemConfig { - type Err = Error; - - fn from_str(s: &str) -> std::result::Result { - match s { - "true" => Ok(Self::Boolean(true)), - "false" => Ok(Self::Boolean(false)), - "also" => Ok(Self::Also), - _ => bail!("Invalid ItemConfig {s}"), - } - } -} - -impl From for ItemConfig { - fn from(value: bool) -> Self { - Self::Boolean(value) - } -} - -impl TryFrom for ItemConfig { - type Error = ::Err; - - fn try_from(value: String) -> std::result::Result { - value.as_str().parse() - } -} - -/// The options available for the revert strings. -#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub enum RevertString { - #[default] - Default, - Debug, - Strip, - VerboseDebug, -} - -impl FromStr for RevertString { - type Err = Error; - - fn from_str(s: &str) -> std::result::Result { - match s { - "default" => Ok(Self::Default), - "debug" => Ok(Self::Debug), - "strip" => Ok(Self::Strip), - "verboseDebug" => Ok(Self::VerboseDebug), - _ => bail!("Invalid RevertString {s}"), - } - } -} - -impl TryFrom for RevertString { - type Error = ::Err; - - fn try_from(value: String) -> std::result::Result { - value.as_str().parse() - } -} - -/// The set of available bytecode formats. -#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum BytecodeFormat { - Legacy, - EofVersionGreaterThanOne, -} - -impl FromStr for BytecodeFormat { - type Err = Error; - - fn from_str(s: &str) -> std::result::Result { - match s { - "legacy" => Ok(Self::Legacy), - ">=EOFv1" => Ok(Self::EofVersionGreaterThanOne), - _ => bail!("Invalid BytecodeFormat {s}"), - } - } -} - -impl TryFrom for BytecodeFormat { - type Error = ::Err; - - fn try_from(value: String) -> std::result::Result { - value.as_str().parse() - } -} - -#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum EvmVersionRequirement { - GreaterThan(EVMVersion), - GreaterThanOrEqual(EVMVersion), - LessThan(EVMVersion), - LessThanOrEqual(EVMVersion), - EqualTo(EVMVersion), -} - -impl FromStr for EvmVersionRequirement { - type Err = Error; - - fn from_str(s: &str) -> std::result::Result { - match s.as_bytes() { - [b'>', b'=', remaining @ ..] => Ok(Self::GreaterThanOrEqual( - str::from_utf8(remaining)?.try_into()?, - )), - [b'>', remaining @ ..] => Ok(Self::GreaterThan(str::from_utf8(remaining)?.try_into()?)), - [b'<', b'=', remaining @ ..] => Ok(Self::LessThanOrEqual( - str::from_utf8(remaining)?.try_into()?, - )), - [b'<', remaining @ ..] => Ok(Self::LessThan(str::from_utf8(remaining)?.try_into()?)), - [b'=', remaining @ ..] => Ok(Self::EqualTo(str::from_utf8(remaining)?.try_into()?)), - _ => bail!("Invalid EVM version requirement {s}"), - } - } -} - -impl TryFrom for EvmVersionRequirement { - type Error = ::Err; - - fn try_from(value: String) -> std::result::Result { - value.as_str().parse() - } -} - #[cfg(test)] mod test { + use indoc::indoc; + use super::*; #[test] fn parses_a_simple_file_correctly() { // Arrange - const SIMPLE_FILE: &str = r#" -==== Source: main.sol ==== -contract C { - function f() public pure returns (uint) { - return 1; - } -} -// ==== -// compileViaYul: true -// ---- -// f() -> 1 -"#; + const SIMPLE_FILE: &str = indoc!( + r#" + ==== Source: main.sol ==== + contract C { + function f() public pure returns (uint) { + return 1; + } + } + // ==== + // compileViaYul: true + // ---- + // f() -> 1 + "# + ); // Act let sections = @@ -462,26 +266,28 @@ contract C { #[test] fn parses_a_complex_file_correctly() { // Arrange - const COMPLEX_FILE: &str = r#" -==== Source: main.sol ==== -import "./lib.sol"; -contract C { - function f() public pure returns (uint) { - return Lib.f(); - } -} -==== Source: lib.sol ==== -library Lib { - function f() internal pure returns (uint) { - return 1; - } -} -// ==== -// compileViaYul: true -// ---- -// # This is a comment -// f() -> 1 -"#; + const COMPLEX_FILE: &str = indoc!( + r#" + ==== Source: main.sol ==== + import "./lib.sol"; + contract C { + function f() public pure returns (uint) { + return Lib.f(); + } + } + ==== Source: lib.sol ==== + library Lib { + function f() internal pure returns (uint) { + return 1; + } + } + // ==== + // compileViaYul: true + // ---- + // # This is a comment + // f() -> 1 + "# + ); // Act let sections = diff --git a/crates/format/src/semantic_tests/test_configuration.rs b/crates/format/src/semantic_tests/test_configuration.rs new file mode 100644 index 0000000..cbc110f --- /dev/null +++ b/crates/format/src/semantic_tests/test_configuration.rs @@ -0,0 +1,200 @@ +use std::str::FromStr; + +use revive_common::EVMVersion; + +use anyhow::{Error, Result, bail}; + +/// The configuration parameters provided in the solidity semantic tests. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct TestConfiguration { + /// Controls if the test case compiles through the Yul IR. + pub compile_via_yul: Option, + /// Controls if the compilation should be done to EWASM. + pub compile_to_ewasm: Option, + /// Controls if ABI encoding should be restricted to the V1 ABI encoder. + pub abi_encoder_v1_only: Option, + /// Controls the EVM Version that the test is compatible with. + pub evm_version: Option, + /// Controls how the revert strings should be handled. + pub revert_strings: Option, + /// Controls if non-existent functions should be permitted or not. + pub allow_non_existing_functions: Option, + /// The list of bytecode formats that this test should be run against. + pub bytecode_format: Option>, +} + +impl TestConfiguration { + pub fn new() -> Self { + Self::default() + } + + pub fn with_config( + &mut self, + key: impl AsRef, + value: impl AsRef, + ) -> Result<&mut Self> { + match key.as_ref() { + "compileViaYul" => self.compile_via_yul = Some(value.as_ref().parse()?), + "compileToEwasm" => self.compile_to_ewasm = Some(value.as_ref().parse()?), + "ABIEncoderV1Only" => self.abi_encoder_v1_only = Some(value.as_ref().parse()?), + "EVMVersion" => self.evm_version = Some(value.as_ref().parse()?), + "revertStrings" => self.revert_strings = Some(value.as_ref().parse()?), + "allowNonExistingFunctions" => { + self.allow_non_existing_functions = Some(value.as_ref().parse()?) + } + "bytecodeFormat" => { + self.bytecode_format = Some( + value + .as_ref() + .split(',') + .map(str::trim) + .map(FromStr::from_str) + .collect::>>()?, + ) + } + _ => bail!("Unknown test configuration {}", key.as_ref()), + }; + Ok(self) + } + + pub fn new_from_pairs( + pairs: impl IntoIterator, impl AsRef)>, + ) -> Result { + let mut this = Self::default(); + pairs + .into_iter() + .try_fold(&mut this, |this, (key, value)| this.with_config(key, value))?; + Ok(this) + } +} + +/// The configuration of a single item in the test configuration. +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ItemConfig { + /// The configuration is set to e a boolean that's either `true` or `false`. + Boolean(bool), + /// The `also` + Also, +} + +impl FromStr for ItemConfig { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + match s { + "true" => Ok(Self::Boolean(true)), + "false" => Ok(Self::Boolean(false)), + "also" => Ok(Self::Also), + _ => bail!("Invalid ItemConfig {s}"), + } + } +} + +impl From for ItemConfig { + fn from(value: bool) -> Self { + Self::Boolean(value) + } +} + +impl TryFrom for ItemConfig { + type Error = ::Err; + + fn try_from(value: String) -> std::result::Result { + value.as_str().parse() + } +} + +/// The options available for the revert strings. +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub enum RevertString { + #[default] + Default, + Debug, + Strip, + VerboseDebug, +} + +impl FromStr for RevertString { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + match s { + "default" => Ok(Self::Default), + "debug" => Ok(Self::Debug), + "strip" => Ok(Self::Strip), + "verboseDebug" => Ok(Self::VerboseDebug), + _ => bail!("Invalid RevertString {s}"), + } + } +} + +impl TryFrom for RevertString { + type Error = ::Err; + + fn try_from(value: String) -> std::result::Result { + value.as_str().parse() + } +} + +/// The set of available bytecode formats. +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum BytecodeFormat { + Legacy, + EofVersionGreaterThanOne, +} + +impl FromStr for BytecodeFormat { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + match s { + "legacy" => Ok(Self::Legacy), + ">=EOFv1" => Ok(Self::EofVersionGreaterThanOne), + _ => bail!("Invalid BytecodeFormat {s}"), + } + } +} + +impl TryFrom for BytecodeFormat { + type Error = ::Err; + + fn try_from(value: String) -> std::result::Result { + value.as_str().parse() + } +} + +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum EvmVersionRequirement { + GreaterThan(EVMVersion), + GreaterThanOrEqual(EVMVersion), + LessThan(EVMVersion), + LessThanOrEqual(EVMVersion), + EqualTo(EVMVersion), +} + +impl FromStr for EvmVersionRequirement { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + match s.as_bytes() { + [b'>', b'=', remaining @ ..] => Ok(Self::GreaterThanOrEqual( + str::from_utf8(remaining)?.try_into()?, + )), + [b'>', remaining @ ..] => Ok(Self::GreaterThan(str::from_utf8(remaining)?.try_into()?)), + [b'<', b'=', remaining @ ..] => Ok(Self::LessThanOrEqual( + str::from_utf8(remaining)?.try_into()?, + )), + [b'<', remaining @ ..] => Ok(Self::LessThan(str::from_utf8(remaining)?.try_into()?)), + [b'=', remaining @ ..] => Ok(Self::EqualTo(str::from_utf8(remaining)?.try_into()?)), + _ => bail!("Invalid EVM version requirement {s}"), + } + } +} + +impl TryFrom for EvmVersionRequirement { + type Error = ::Err; + + fn try_from(value: String) -> std::result::Result { + value.as_str().parse() + } +}