Skip to content

Commit

Permalink
Feat: add rust tests for jsontests (#259)
Browse files Browse the repository at this point in the history
* Added rust jsontests

* Revert CI
  • Loading branch information
mrLSD committed Dec 7, 2023
1 parent ae87a5b commit 1e1aac7
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 79 deletions.
1 change: 0 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,3 @@ jobs:
jsontests/res/ethtests/GeneralStateTests/VMTests/vmIOandFlowOperations/ \
jsontests/res/ethtests/GeneralStateTests/VMTests/vmLogTest/ \
jsontests/res/ethtests/GeneralStateTests/VMTests/vmTests/
1 change: 1 addition & 0 deletions jsontests/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use thiserror::Error;

#[derive(Error, Debug)]
#[allow(dead_code)]
pub enum TestError {
#[error("state root is different")]
StateMismatch,
Expand Down
75 changes: 75 additions & 0 deletions jsontests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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();
}
69 changes: 1 addition & 68 deletions jsontests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -22,74 +17,12 @@ struct Cli {
debug: bool,
}

fn run_file(filename: &str, debug: bool) -> Result<TestCompletionStatus, Error> {
let test_multi: BTreeMap<String, TestMulti> =
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<TestCompletionStatus, Error> {
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();

Expand Down
89 changes: 87 additions & 2 deletions jsontests/src/run.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,100 @@
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;
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<TestCompletionStatus, Error> {
let test_multi: BTreeMap<String, TestMulti> =
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<TestCompletionStatus, Error> {
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),
Expand Down
26 changes: 18 additions & 8 deletions jsontests/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -40,29 +40,31 @@ 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
);
}

// 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
);
}
}

/// `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")]
Expand All @@ -74,12 +76,15 @@ pub struct TestMulti {
}

impl TestMulti {
pub fn tests(&self) -> Vec<Test> {
/// 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<TestData> {
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,
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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,
Expand Down Expand Up @@ -176,6 +185,7 @@ pub struct TestPostState {
pub expect_exception: Option<TestExpectException>,
}

/// `TestExpectException` expected Ethereum exception
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
#[allow(non_camel_case_types)]
pub enum TestExpectException {
Expand Down

0 comments on commit 1e1aac7

Please sign in to comment.