use ethjson::test::{ ChainTests, DifficultyTests, EthereumTestSuite, ExecutiveTests, StateTests, TestChainSpec, TestTrieSpec, TransactionTests, TrieTests, }; use globset::Glob; use log::info; use rayon::prelude::*; use std::path::{Path, PathBuf}; use tempfile::tempdir; use trie::TrieSpec; /// Result of tests execution pub struct TestResult { /// Number of success execution pub success: usize, /// Number of success execution pub failed: Vec, } impl TestResult { /// Creates a new TestResult without results pub fn zero() -> Self { TestResult { success: 0, failed: Vec::new(), } } /// Creates a new success TestResult pub fn success() -> Self { TestResult { success: 1, failed: Vec::new(), } } /// Creates a new failed TestResult pub fn failed(name: &str) -> Self { TestResult { success: 0, failed: vec![name.to_string()], } } } impl std::ops::Add for TestResult { type Output = Self; fn add(self, other: Self) -> Self { let mut mself = self; mself.success += other.success; mself.failed.extend_from_slice(&other.failed); mself } } impl std::ops::AddAssign for TestResult { fn add_assign(&mut self, other: Self) { self.success += other.success; self.failed.extend_from_slice(&other.failed); } } /// An executor of ethereum/json tests pub struct TestRunner(EthereumTestSuite); impl TestRunner { /// Loads a new JSON Test suite pub fn load(reader: R) -> Result where R: std::io::Read, { Ok(TestRunner(serde_json::from_reader(reader)?)) } /// Run the tests with one thread pub fn run_without_par(&self) -> TestResult { let pool = rayon::ThreadPoolBuilder::new() .num_threads(1) .build() .unwrap(); pool.install(|| self.run()) } /// Run the tests pub fn run(&self) -> TestResult { let mut res = TestResult::zero(); for t in &self.0.chain { res += Self::run_chain_tests(&t); } for t in &self.0.state { res += Self::run_state_tests(&t); } for t in &self.0.difficulty { res += Self::run_difficuly_tests(&t); } for t in &self.0.executive { res += Self::run_executive_tests(&t); } for t in &self.0.transaction { res += Self::run_transaction_tests(&t); } for t in &self.0.trie { res += Self::run_trie_tests(&t); } res } fn run1(test: &T, base_path: &PathBuf, f: F) -> TestResult where T: Send + Sync, F: Fn(&T, &Path, &[u8]) -> Vec + Send + Sync, { let result = super::find_json_files_recursive(&base_path) .into_par_iter() .map(|path| { info!("{:?}", path); let json = std::fs::read(&path).unwrap(); let faileds = f(test, &path, &json); if faileds.len() > 0 { TestResult::failed(&faileds.join(",")) } else { TestResult::success() } }) .reduce(TestResult::zero, |a, b| a + b); if result.success + result.failed.len() == 0 { panic!("There is no tests in the specified path {:?}", base_path); } result } fn in_set(path: &Path, exprs: &[String]) -> bool { for pathexp in exprs { let glob = Glob::new(&pathexp) .expect(&format!("cannot parse expression {}", pathexp)) .compile_matcher(); if glob.is_match(path) { return true; } } false } fn run_chain_tests(test: &ChainTests) -> TestResult { Self::run1( test, &test.path, |test: &ChainTests, path: &Path, json: &[u8]| { for skip in &test.skip { if Self::in_set(&path, &skip.paths) { println!(" - {} ..SKIPPED", path.to_string_lossy()); return Vec::new(); } } super::chain::json_chain_test(&test, &path, &json, &mut |_, _| {}) }, ) } fn run_state_tests(test: &StateTests) -> TestResult { Self::run1( test, &test.path, |test: &StateTests, path: &Path, json: &[u8]| { for skip in &test.skip { if Self::in_set(&path, &skip.paths) { println!(" - {} ..SKIPPED", path.to_string_lossy()); return Vec::new(); } } super::state::json_chain_test(&test, &path, &json, &mut |_, _| {}) }, ) } fn run_difficuly_tests(test: &DifficultyTests) -> TestResult { let mut acc = TestResult::zero(); for path in &test.path { acc += Self::run1( test, &path, |test: &DifficultyTests, path: &Path, json: &[u8]| { let spec = match &test.chainspec { TestChainSpec::Foundation => { crate::ethereum::new_foundation(&tempdir().unwrap().path()) } TestChainSpec::ByzantiumTest => crate::ethereum::new_byzantium_test(), TestChainSpec::FrontierTest => crate::ethereum::new_frontier_test(), TestChainSpec::HomesteadTest => crate::ethereum::new_homestead_test(), }; super::difficulty::json_difficulty_test(&path, &json, spec, &mut |_, _| {}) }, ) } acc } fn run_executive_tests(test: &ExecutiveTests) -> TestResult { Self::run1( test, &test.path, |_: &ExecutiveTests, path: &Path, json: &[u8]| { super::executive::json_executive_test(&path, &json, &mut |_, _| {}) }, ) } fn run_transaction_tests(test: &TransactionTests) -> TestResult { Self::run1( test, &test.path, |_: &TransactionTests, path: &Path, json: &[u8]| { super::transaction::json_transaction_test(&path, &json, &mut |_, _| {}) }, ) } fn run_trie_tests(test: &TrieTests) -> TestResult { let mut acc = TestResult::zero(); for path in &test.path { acc += Self::run1(test, &path, |test: &TrieTests, path: &Path, json: &[u8]| { let spec = match &test.triespec { TestTrieSpec::Generic => TrieSpec::Generic, TestTrieSpec::Secure => TrieSpec::Secure, }; super::trie::json_trie_test(&path, &json, spec, &mut |_, _| {}) }); } acc } } #[test] fn ethereum_json_tests() { let content = std::fs::read("res/json_tests.json").expect("cannot open ethereum tests spec file"); let runner = TestRunner::load(content.as_slice()).expect("cannot load ethereum tests spec file"); println!("----------------------------------------------------"); let result = match std::env::var_os("TEST_DEBUG") { Some(_) => runner.run_without_par(), _ => runner.run(), }; println!("----------------------------------------------------"); flushln!( "SUCCESS: {} FAILED: {} {:?}", result.success, result.failed.len(), result.failed ); assert!(result.failed.len() == 0); }