diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ae2eff533..8de37da97 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -53,4 +53,3 @@ jobs: jsontests/res/ethtests/GeneralStateTests/VMTests/vmIOandFlowOperations/ \ jsontests/res/ethtests/GeneralStateTests/VMTests/vmLogTest/ \ jsontests/res/ethtests/GeneralStateTests/VMTests/vmTests/ - diff --git a/jsontests/src/error.rs b/jsontests/src/error.rs index 125f07d26..b492a1e04 100644 --- a/jsontests/src/error.rs +++ b/jsontests/src/error.rs @@ -2,6 +2,7 @@ use thiserror::Error; #[derive(Error, Debug)] +#[allow(dead_code)] pub enum TestError { #[error("state root is different")] StateMismatch, diff --git a/jsontests/src/lib.rs b/jsontests/src/lib.rs new file mode 100644 index 000000000..3dca24ae6 --- /dev/null +++ b/jsontests/src/lib.rs @@ -0,0 +1,75 @@ +pub mod error; +pub mod hash; +pub mod in_memory; +pub mod run; +pub mod types; + +#[test] +fn st_args_zero_one_balance() { + const JSON_FILENAME: &str = "res/ethtests/GeneralStateTests/stArgsZeroOneBalance/"; + let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); + tests_status.print_total(); +} + +#[test] +fn st_code_copy_test() { + const JSON_FILENAME: &str = "res/ethtests/GeneralStateTests/stCodeCopyTest/"; + let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); + tests_status.print_total(); +} + +#[test] +fn st_example() { + const JSON_FILENAME: &str = "res/ethtests/GeneralStateTests/stExample/"; + let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); + tests_status.print_total(); +} + +#[test] +fn st_self_balance() { + const JSON_FILENAME: &str = "res/ethtests/GeneralStateTests/stSelfBalance/"; + let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); + tests_status.print_total(); +} + +#[test] +fn st_s_load_test() { + const JSON_FILENAME: &str = "res/ethtests/GeneralStateTests/stSLoadTest/"; + let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); + tests_status.print_total(); +} + +#[test] +fn vm_arithmetic_test() { + const JSON_FILENAME: &str = "res/ethtests/GeneralStateTests/VMTests/vmArithmeticTest/"; + let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); + tests_status.print_total(); +} + +#[test] +fn vm_bitwise_logic_operation() { + const JSON_FILENAME: &str = "res/ethtests/GeneralStateTests/VMTests/vmBitwiseLogicOperation/"; + let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); + tests_status.print_total(); +} + +#[test] +fn vm_io_and_flow_operations() { + const JSON_FILENAME: &str = "res/ethtests/GeneralStateTests/VMTests/vmIOandFlowOperations/"; + let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); + tests_status.print_total(); +} + +#[test] +fn vm_log_test() { + const JSON_FILENAME: &str = "res/ethtests/GeneralStateTests/VMTests/vmLogTest/"; + let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); + tests_status.print_total(); +} + +#[test] +fn vm_tests() { + const JSON_FILENAME: &str = "res/ethtests/GeneralStateTests/VMTests/vmTests/"; + let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); + tests_status.print_total(); +} diff --git a/jsontests/src/main.rs b/jsontests/src/main.rs index 01c5b5b75..390571365 100644 --- a/jsontests/src/main.rs +++ b/jsontests/src/main.rs @@ -7,11 +7,6 @@ mod types; use crate::error::Error; use crate::types::*; use clap::Parser; -use std::collections::BTreeMap; -use std::fs::{self, File}; -use std::io::BufReader; - -const BASIC_FILE_PATH: &str = "jsontests/res/ethtests/GeneralStateTests/"; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -22,74 +17,12 @@ struct Cli { debug: bool, } -fn run_file(filename: &str, debug: bool) -> Result { - let test_multi: BTreeMap = - serde_json::from_reader(BufReader::new(File::open(filename)?))?; - let mut tests_status = TestCompletionStatus::default(); - - for (test_name, test_multi) in test_multi { - let tests = test_multi.tests(); - let short_file_name = filename.replace(BASIC_FILE_PATH, ""); - for test in &tests { - if debug { - print!( - "[{:?}] {} | {}/{} DEBUG: ", - test.fork, short_file_name, test_name, test.index - ); - } else { - print!( - "[{:?}] {} | {}/{}: ", - test.fork, short_file_name, test_name, test.index - ); - } - match run::run_test(filename, &test_name, test.clone(), debug) { - Ok(()) => { - tests_status.inc_completed(); - println!("ok") - } - Err(Error::UnsupportedFork) => { - tests_status.inc_skipped(); - println!("skipped") - } - Err(err) => { - println!("ERROR: {:?}", err); - return Err(err); - } - } - if debug { - println!(); - } - } - - tests_status.print_completion(); - } - - Ok(tests_status) -} - -fn run_single(filename: &str, debug: bool) -> Result { - if fs::metadata(filename)?.is_dir() { - let mut tests_status = TestCompletionStatus::default(); - - for filename in fs::read_dir(filename)? { - let filepath = filename?.path(); - let filename = filepath.to_str().ok_or(Error::NonUtf8Filename)?; - println!("RUM for: {filename}"); - tests_status += run_file(filename, debug)?; - } - tests_status.print_total_for_dir(filename); - Ok(tests_status) - } else { - run_file(filename, debug) - } -} - fn main() -> Result<(), Error> { let cli = Cli::parse(); let mut tests_status = TestCompletionStatus::default(); for filename in cli.filenames { - tests_status += run_single(&filename, cli.debug)?; + tests_status += run::run_single(&filename, cli.debug)?; } tests_status.print_total(); diff --git a/jsontests/src/run.rs b/jsontests/src/run.rs index c5306426f..835abbdeb 100644 --- a/jsontests/src/run.rs +++ b/jsontests/src/run.rs @@ -1,6 +1,6 @@ use crate::error::{Error, TestError}; use crate::in_memory::{InMemoryAccount, InMemoryBackend, InMemoryEnvironment, InMemoryLayer}; -use crate::types::*; +use crate::types::{Fork, TestCompletionStatus, TestData, TestExpectException, TestMulti}; use evm::standard::{Config, Etable, EtableResolver, Invoker, TransactArgs}; use evm::utils::u256_to_h256; use evm::Capture; @@ -8,8 +8,93 @@ use evm::{interpreter::Interpreter, GasState}; use evm_precompile::StandardPrecompileSet; use primitive_types::U256; use std::collections::{BTreeMap, BTreeSet}; +use std::fs::{self, File}; +use std::io::BufReader; -pub fn run_test(_filename: &str, _test_name: &str, test: Test, debug: bool) -> Result<(), Error> { +const BASIC_FILE_PATH_TO_TRIM: [&str; 2] = [ + "jsontests/res/ethtests/GeneralStateTests/", + "res/ethtests/GeneralStateTests/", +]; + +fn get_short_file_name(filename: &str) -> String { + let mut short_file_name = String::from(filename); + for pattern in BASIC_FILE_PATH_TO_TRIM { + short_file_name = short_file_name.replace(pattern, ""); + } + short_file_name.clone().to_string() +} + +/// Run tests for specific json file with debug flag +fn run_file(filename: &str, debug: bool) -> Result { + let test_multi: BTreeMap = + serde_json::from_reader(BufReader::new(File::open(filename)?))?; + let mut tests_status = TestCompletionStatus::default(); + + for (test_name, test_multi) in test_multi { + let tests = test_multi.tests(); + let short_file_name = get_short_file_name(filename); + for test in &tests { + if debug { + print!( + "[{:?}] {} | {}/{} DEBUG: ", + test.fork, short_file_name, test_name, test.index + ); + } else { + print!( + "[{:?}] {} | {}/{}: ", + test.fork, short_file_name, test_name, test.index + ); + } + match run_test(filename, &test_name, test.clone(), debug) { + Ok(()) => { + tests_status.inc_completed(); + println!("ok") + } + Err(Error::UnsupportedFork) => { + tests_status.inc_skipped(); + println!("skipped") + } + Err(err) => { + println!("ERROR: {:?}", err); + return Err(err); + } + } + if debug { + println!(); + } + } + + tests_status.print_completion(); + } + + Ok(tests_status) +} + +/// Run test for single json file or directory +pub fn run_single(filename: &str, debug: bool) -> Result { + if fs::metadata(filename)?.is_dir() { + let mut tests_status = TestCompletionStatus::default(); + + for filename in fs::read_dir(filename)? { + let filepath = filename?.path(); + let filename = filepath.to_str().ok_or(Error::NonUtf8Filename)?; + println!("RUM for: {filename}"); + tests_status += run_file(filename, debug)?; + } + tests_status.print_total_for_dir(filename); + Ok(tests_status) + } else { + run_file(filename, debug) + } +} + +/// Run single test +pub fn run_test( + _filename: &str, + _test_name: &str, + test: TestData, + debug: bool, +) -> Result<(), Error> { let config = match test.fork { Fork::Berlin => Config::berlin(), _ => return Err(Error::UnsupportedFork), diff --git a/jsontests/src/types.rs b/jsontests/src/types.rs index 8b3eea473..82b6be9f4 100644 --- a/jsontests/src/types.rs +++ b/jsontests/src/types.rs @@ -9,7 +9,7 @@ use std::fmt; /// Statistic type to gather tests pass completion status #[derive(Default, Clone, Debug, Eq, PartialEq)] -pub(crate) struct TestCompletionStatus { +pub struct TestCompletionStatus { pub completed: usize, pub skipped: usize, } @@ -40,14 +40,14 @@ impl TestCompletionStatus { /// Print completion status. /// Most useful for single file completion statistic info pub fn print_completion(&self) { - println!("COMPLETED: {:?} tests", self.completed); - println!("SKIPPED: {:?} tests\n", self.skipped); + println!("COMPLETED: {} tests", self.completed); + println!("SKIPPED: {} tests\n", self.skipped); } /// Print tests pass total statistic info for directory pub fn print_total_for_dir(&self, filename: &str) { println!( - "TOTAL tests for: {filename}\n\tCOMPLETED: {:?}\n\tSKIPPED: {:?}", + "TOTAL tests for: {filename}\n\tCOMPLETED: {}\n\tSKIPPED: {}", self.completed, self.skipped ); } @@ -55,7 +55,7 @@ impl TestCompletionStatus { // Print total statistics info pub fn print_total(&self) { println!( - "\nTOTAL: {:?} tests\n\tCOMPLETED: {:?}\n\tSKIPPED: {:?}", + "\nTOTAL: {} tests\n\tCOMPLETED: {}\n\tSKIPPED: {}", self.get_total(), self.completed, self.skipped @@ -63,6 +63,8 @@ impl TestCompletionStatus { } } +/// `TestMulti` represents raw data from `jsontest` data file. +/// It contains multiple test data for passing tests. #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] pub struct TestMulti { #[serde(rename = "_info")] @@ -74,12 +76,15 @@ pub struct TestMulti { } impl TestMulti { - pub fn tests(&self) -> Vec { + /// Fill tests data from `TestMulti` data. + /// Return array of `TestData`, that represent single test, + /// that ready to pass the test flow. + pub fn tests(&self) -> Vec { let mut tests = Vec::new(); for (fork, post_states) in &self.post { for (index, post_state) in post_states.iter().enumerate() { - tests.push(Test { + tests.push(TestData { info: self.info.clone(), env: self.env.clone(), fork: *fork, @@ -108,8 +113,9 @@ impl TestMulti { } } +/// Structure that contains data to run single test #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Test { +pub struct TestData { pub info: TestInfo, pub env: TestEnv, pub fork: Fork, @@ -119,6 +125,7 @@ pub struct Test { pub transaction: TestTransaction, } +/// `TestInfo` contains information data about test from json file #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TestInfo { @@ -134,6 +141,7 @@ pub struct TestInfo { pub source_hash: String, } +/// `TestEnv` represents Ethereum environment data #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TestEnv { @@ -149,6 +157,7 @@ pub struct TestEnv { pub previous_hash: H256, } +/// Available Ethereum forks for testing #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Deserialize)] pub enum Fork { Berlin, @@ -176,6 +185,7 @@ pub struct TestPostState { pub expect_exception: Option, } +/// `TestExpectException` expected Ethereum exception #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] #[allow(non_camel_case_types)] pub enum TestExpectException {