Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add rust tests for jsontests #259

Merged
merged 2 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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