// Copyright 2015-2019 Parity Technologies (UK) Ltd. // This file is part of Parity Ethereum. // Parity Ethereum is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity Ethereum is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . use std::collections::HashSet; use std::io::Read; use std::fs::{File, read_dir}; use std::path::Path; use std::ffi::OsString; pub use ethereum_types::{H256, U256, Address}; /// Indicate when to run the hook passed to test functions. #[derive(Copy, Clone, Eq, PartialEq)] pub enum HookType { /// Hook to code to run on test start. OnStart, /// Hook to code to run on test end. OnStop } /// Run all tests under the given path (except for the test files named in the skip list) using the /// provided runner function. pub fn run_test_path( path: &Path, skip: &[&'static str], runner: fn(path: &Path, json_data: &[u8], start_stop_hook: &mut H) -> Vec, start_stop_hook: &mut H ) { if !skip.is_empty() { // todo[dvdplm] it's really annoying to have to use flushln here. Should be `info!(target: // "json-tests", …)`. Issue https://github.com/paritytech/parity-ethereum/issues/11084 flushln!("[run_test_path] Skipping tests in {}: {:?}", path.display(), skip); } let mut errors = Vec::new(); run_test_path_inner(path, skip, runner, start_stop_hook, &mut errors); let empty: [String; 0] = []; assert_eq!(errors, empty, "\nThere were {} tests in '{}' that failed.", errors.len(), path.display()); } fn run_test_path_inner( p: &Path, skip: &[&'static str], runner: fn(path: &Path, json_data: &[u8], start_stop_hook: &mut H) -> Vec, start_stop_hook: &mut H, errors: &mut Vec ) { let path = Path::new(p); let extension = path.extension().and_then(|s| s.to_str()); let skip_list: HashSet = skip.iter().map(|s| { let mut os: OsString = s.into(); os.push(".json"); os }).collect(); if path.is_dir() { trace!(target: "json-tests", "running tests contained in '{}'", path.display()); let test_files = read_dir(path) .expect("Directory exists on disk") .filter_map(|dir_entry| { let dir_entry = dir_entry.expect("Entry in directory listing exists"); if skip_list.contains(&dir_entry.file_name()) { debug!(target: "json-tests", "'{:?}' is on the skip list.", dir_entry.file_name()); None } else { Some(dir_entry.path()) } }); for test_file in test_files { run_test_path_inner(&test_file, skip, runner, start_stop_hook, errors); } } else if extension == Some("swp") || extension == None { trace!(target: "json-tests", "ignoring '{}', extension {:?} – Junk?", path.display(), extension); // Ignore junk } else { trace!(target: "json-tests", "running tests in '{}'", path.display()); let mut path = p.to_path_buf(); path.set_extension("json"); run_test_file_append(&path, runner, start_stop_hook, errors) } } fn run_test_file_append( path: &Path, runner: fn(path: &Path, json_data: &[u8], start_stop_hook: &mut H) -> Vec, start_stop_hook: &mut H, errors: &mut Vec ) { let mut data = Vec::new(); let mut file = match File::open(&path) { Ok(file) => file, Err(_) => panic!("Error opening test file at: {:?}", path), }; file.read_to_end(&mut data).expect("Error reading test file"); errors.append(&mut runner(&path, &data, start_stop_hook)); } pub fn run_test_file( path: &Path, runner: fn(path: &Path, json_data: &[u8], start_stop_hook: &mut H) -> Vec, start_stop_hook: &mut H ) { let mut data = Vec::new(); let mut file = match File::open(&path) { Ok(file) => file, Err(_) => panic!("Error opening test file at: {:?}", path), }; file.read_to_end(&mut data).expect("Error reading test file"); let results = runner(&path, &data, start_stop_hook); let empty: [String; 0] = []; assert_eq!(results, empty); } #[cfg(test)] macro_rules! test { ($name: expr, $skip: expr) => { ::json_tests::test_common::run_test_path( ::std::path::Path::new(concat!("res/ethereum/tests/", $name)), &$skip, do_json_test, &mut |_, _| () ); } } /// Declares a test: /// /// declare_test!(test_name, "path/to/folder/with/tests"); /// /// Declares a test but skip the named test files inside the folder (no extension): /// /// declare_test!(skip => ["a-test-file", "other-test-file"], test_name, "path/to/folder/with/tests"); /// /// NOTE: a skipped test is considered a passing test as far as `cargo test` is concerned. Normally /// one test corresponds to a folder full of test files, each of which may contain many tests. #[macro_export] macro_rules! declare_test { (skip => $arr: expr, $id: ident, $name: expr) => { #[cfg(test)] #[test] #[allow(non_snake_case)] fn $id() { test!($name, $arr); } }; (ignore => $id: ident, $name: expr) => { #[cfg(test)] #[ignore] #[test] #[allow(non_snake_case)] fn $id() { test!($name, []); } }; (heavy => $id: ident, $name: expr) => { #[cfg(test)] #[cfg(feature = "test-heavy")] #[test] #[allow(non_snake_case)] fn $id() { test!($name, []); } }; ($id: ident, $name: expr) => { #[cfg(test)] #[test] #[allow(non_snake_case)] fn $id() { test!($name, []); } } }