From cf509bea9b93a1e51f0e0118613fb5eefaeb436b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 13 May 2019 22:10:23 +1000 Subject: [PATCH] Improve ef_tests crate --- eth2/utils/ssz/src/decode/impls.rs | 44 ++++++++++++++++++- eth2/utils/ssz/src/encode/impls.rs | 38 ++++++++++++++++- tests/ef_tests/Cargo.toml | 3 ++ tests/ef_tests/src/error.rs | 9 ++++ tests/ef_tests/src/lib.rs | 68 ++++++++++++++++++++++++++++++ tests/ef_tests/src/test_decode.rs | 35 +++++++++++++++ tests/ef_tests/tests/tests.rs | 12 +++++- 7 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 tests/ef_tests/src/error.rs create mode 100644 tests/ef_tests/src/test_decode.rs diff --git a/eth2/utils/ssz/src/decode/impls.rs b/eth2/utils/ssz/src/decode/impls.rs index 8a5a36780..4137b5a56 100644 --- a/eth2/utils/ssz/src/decode/impls.rs +++ b/eth2/utils/ssz/src/decode/impls.rs @@ -1,5 +1,5 @@ use super::*; -use ethereum_types::H256; +use ethereum_types::{H256, U128, U256}; macro_rules! impl_decodable_for_uint { ($type: ident, $bit_size: expr) => { @@ -85,6 +85,48 @@ impl Decode for H256 { } } +impl Decode for U256 { + fn is_ssz_fixed_len() -> bool { + true + } + + fn ssz_fixed_len() -> usize { + 32 + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + let len = bytes.len(); + let expected = ::ssz_fixed_len(); + + if len != expected { + Err(DecodeError::InvalidByteLength { len, expected }) + } else { + Ok(U256::from_little_endian(bytes)) + } + } +} + +impl Decode for U128 { + fn is_ssz_fixed_len() -> bool { + true + } + + fn ssz_fixed_len() -> usize { + 16 + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + let len = bytes.len(); + let expected = ::ssz_fixed_len(); + + if len != expected { + Err(DecodeError::InvalidByteLength { len, expected }) + } else { + Ok(U128::from_little_endian(bytes)) + } + } +} + macro_rules! impl_decodable_for_u8_array { ($len: expr) => { impl Decode for [u8; $len] { diff --git a/eth2/utils/ssz/src/encode/impls.rs b/eth2/utils/ssz/src/encode/impls.rs index 07886d68f..1202d81fd 100644 --- a/eth2/utils/ssz/src/encode/impls.rs +++ b/eth2/utils/ssz/src/encode/impls.rs @@ -1,5 +1,5 @@ use super::*; -use ethereum_types::H256; +use ethereum_types::{H256, U128, U256}; macro_rules! impl_encodable_for_uint { ($type: ident, $bit_size: expr) => { @@ -77,6 +77,42 @@ impl Encode for H256 { } } +impl Encode for U256 { + fn is_ssz_fixed_len() -> bool { + true + } + + fn ssz_fixed_len() -> usize { + 32 + } + + fn ssz_append(&self, buf: &mut Vec) { + let n = ::ssz_fixed_len(); + let s = buf.len(); + + buf.resize(s + n, 0); + self.to_little_endian(&mut buf[s..]); + } +} + +impl Encode for U128 { + fn is_ssz_fixed_len() -> bool { + true + } + + fn ssz_fixed_len() -> usize { + 16 + } + + fn ssz_append(&self, buf: &mut Vec) { + let n = ::ssz_fixed_len(); + let s = buf.len(); + + buf.resize(s + n, 0); + self.to_little_endian(&mut buf[s..]); + } +} + macro_rules! impl_encodable_for_u8_array { ($len: expr) => { impl Encode for [u8; $len] { diff --git a/tests/ef_tests/Cargo.toml b/tests/ef_tests/Cargo.toml index 367cbbe14..8a5cc4372 100644 --- a/tests/ef_tests/Cargo.toml +++ b/tests/ef_tests/Cargo.toml @@ -5,6 +5,9 @@ authors = ["Paul Hauner "] edition = "2018" [dependencies] +ethereum-types = "0.5" +hex = "0.3" serde = "1.0" serde_derive = "1.0" serde_yaml = "0.8" +ssz = { path = "../../eth2/utils/ssz" } diff --git a/tests/ef_tests/src/error.rs b/tests/ef_tests/src/error.rs new file mode 100644 index 000000000..58732e83e --- /dev/null +++ b/tests/ef_tests/src/error.rs @@ -0,0 +1,9 @@ +#[derive(Debug, PartialEq, Clone)] +pub enum Error { + /// The value in the test didn't match our value. + NotEqual(String), + /// The test specified a failure and we did not experience one. + DidntFail(String), + /// Failed to parse the test (internal error). + FailedToParseTest(String), +} diff --git a/tests/ef_tests/src/lib.rs b/tests/ef_tests/src/lib.rs index f7308c32b..828e2615c 100644 --- a/tests/ef_tests/src/lib.rs +++ b/tests/ef_tests/src/lib.rs @@ -1,4 +1,12 @@ +use error::Error; use serde_derive::Deserialize; +use ssz::Decode; +use std::fmt::Debug; +use ethereum_types::{U256, U128}; +use test_decode::TestDecode; + +mod error; +mod test_decode; #[derive(Debug, Deserialize)] pub struct TestDoc { @@ -21,6 +29,66 @@ pub struct SszGenericCase { pub ssz: Option, } +pub trait Test { + fn test(&self) -> Vec>; +} + +impl Test for TestDoc { + fn test(&self) -> Vec> { + self + .test_cases + .iter() + .map(|tc| { + if let Some(ssz) = &tc.ssz { + match tc.type_name.as_ref() { + "uint8" => compare_decoding::(tc.valid, ssz, &tc.value), + "uint16" => compare_decoding::(tc.valid, ssz, &tc.value), + "uint32" => compare_decoding::(tc.valid, ssz, &tc.value), + "uint64" => compare_decoding::(tc.valid, ssz, &tc.value), + "uint128" => compare_decoding::(tc.valid, ssz, &tc.value), + "uint256" => compare_decoding::(tc.valid, ssz, &tc.value), + _ => { + Err(Error::FailedToParseTest(format!("Unknown type: {}", tc.type_name))) + } + } + } else { + // Skip tests that do not have an ssz field. + // + // See: https://github.com/ethereum/eth2.0-specs/issues/1079 + Ok(()) + } + }) + .collect() + } +} + +fn compare_decoding(should_pass: bool, ssz: &String, value: &String) -> Result<(), Error> +where + T: Decode + TestDecode + Debug + PartialEq, +{ + let ssz = hex::decode(&ssz[2..]) + .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + let expected = T::test_decode(value)?; + + let decoded = T::from_ssz_bytes(&ssz); + + if should_pass { + let decoded = decoded.map_err(|e| Error::NotEqual(format!("{:?}", e)))?; + + if decoded != expected { + Err(Error::NotEqual(format!("{:?} != {:?}", decoded, expected))) + } else { + Ok(()) + } + } else { + if let Ok(decoded) = decoded { + Err(Error::DidntFail(format!("Decoded as {:?}", decoded))) + } else { + Ok(()) + } + } +} + #[cfg(test)] mod tests { #[test] diff --git a/tests/ef_tests/src/test_decode.rs b/tests/ef_tests/src/test_decode.rs new file mode 100644 index 000000000..dbbbcdae0 --- /dev/null +++ b/tests/ef_tests/src/test_decode.rs @@ -0,0 +1,35 @@ +use super::*; + +pub trait TestDecode: Sized { + fn test_decode(string: &String) -> Result; +} + +macro_rules! impl_via_parse { + ($ty: ty) => { + impl TestDecode for $ty { + fn test_decode(string: &String) -> Result { + string + .parse::() + .map_err(|e| Error::FailedToParseTest(format!("{:?}", e))) + } + } + }; +} + +impl_via_parse!(u8); +impl_via_parse!(u16); +impl_via_parse!(u32); +impl_via_parse!(u64); + +macro_rules! impl_via_from_dec_str { + ($ty: ty) => { + impl TestDecode for $ty { + fn test_decode(string: &String) -> Result { + Self::from_dec_str(string).map_err(|e| Error::FailedToParseTest(format!("{:?}", e))) + } + } + }; +} + +impl_via_from_dec_str!(U128); +impl_via_from_dec_str!(U256); diff --git a/tests/ef_tests/tests/tests.rs b/tests/ef_tests/tests/tests.rs index 023b7c18a..46fa4e9cb 100644 --- a/tests/ef_tests/tests/tests.rs +++ b/tests/ef_tests/tests/tests.rs @@ -21,7 +21,15 @@ fn load_test_case(test_name: &str) -> TestDoc { fn ssz() { let doc: TestDoc = load_test_case("ssz_generic/uint/uint_bounds.yaml"); - dbg!(doc); + let results = doc.test(); - assert!(false); + let failures: Vec<(usize, &Result<_, _>)> = results + .iter() + .enumerate() + .filter(|(_i, r)| r.is_ok()) + .collect(); + + if !failures.is_empty() { + panic!("{:?}", failures); + } }