BLS and SSZ static tests
This commit is contained in:
parent
aed2f6407d
commit
23a308e595
@ -4,25 +4,13 @@ use crate::{Checkpoint, Crosslink, Hash256};
|
|||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use ssz_derive::{Decode, Encode};
|
use ssz_derive::{Decode, Encode};
|
||||||
use test_random_derive::TestRandom;
|
use test_random_derive::TestRandom;
|
||||||
use tree_hash::TreeHash;
|
use tree_hash_derive::TreeHash;
|
||||||
use tree_hash_derive::{SignedRoot, TreeHash};
|
|
||||||
|
|
||||||
/// The data upon which an attestation is based.
|
/// The data upon which an attestation is based.
|
||||||
///
|
///
|
||||||
/// Spec v0.8.0
|
/// Spec v0.8.0
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, Encode, Decode, TreeHash, TestRandom,
|
||||||
Clone,
|
|
||||||
PartialEq,
|
|
||||||
Eq,
|
|
||||||
Serialize,
|
|
||||||
Deserialize,
|
|
||||||
Hash,
|
|
||||||
Encode,
|
|
||||||
Decode,
|
|
||||||
TreeHash,
|
|
||||||
TestRandom,
|
|
||||||
SignedRoot,
|
|
||||||
)]
|
)]
|
||||||
pub struct AttestationData {
|
pub struct AttestationData {
|
||||||
// LMD GHOST vote
|
// LMD GHOST vote
|
||||||
|
@ -4,7 +4,7 @@ use serde_derive::{Deserialize, Serialize};
|
|||||||
use ssz_derive::{Decode, Encode};
|
use ssz_derive::{Decode, Encode};
|
||||||
use test_random_derive::TestRandom;
|
use test_random_derive::TestRandom;
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
use tree_hash_derive::{SignedRoot, TreeHash};
|
use tree_hash_derive::TreeHash;
|
||||||
|
|
||||||
/// Casper FFG checkpoint, used in attestations.
|
/// Casper FFG checkpoint, used in attestations.
|
||||||
///
|
///
|
||||||
@ -22,7 +22,6 @@ use tree_hash_derive::{SignedRoot, TreeHash};
|
|||||||
Decode,
|
Decode,
|
||||||
TreeHash,
|
TreeHash,
|
||||||
TestRandom,
|
TestRandom,
|
||||||
SignedRoot,
|
|
||||||
)]
|
)]
|
||||||
pub struct Checkpoint {
|
pub struct Checkpoint {
|
||||||
pub epoch: Epoch,
|
pub epoch: Epoch,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
mod bls_aggregate_pubkeys;
|
mod bls_aggregate_pubkeys;
|
||||||
mod bls_aggregate_sigs;
|
mod bls_aggregate_sigs;
|
||||||
@ -53,6 +54,11 @@ pub use shuffling::*;
|
|||||||
pub use ssz_generic::*;
|
pub use ssz_generic::*;
|
||||||
pub use ssz_static::*;
|
pub use ssz_static::*;
|
||||||
|
|
||||||
|
pub trait LoadCase: Sized {
|
||||||
|
/// Load the test case from a test case directory.
|
||||||
|
fn load_from_dir(_path: &Path) -> Result<Self, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Case: Debug {
|
pub trait Case: Debug {
|
||||||
/// An optional field for implementing a custom description.
|
/// An optional field for implementing a custom description.
|
||||||
///
|
///
|
||||||
@ -68,6 +74,26 @@ pub trait Case: Debug {
|
|||||||
fn result(&self, case_index: usize) -> Result<(), Error>;
|
fn result(&self, case_index: usize) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait BlsCase: serde::de::DeserializeOwned {}
|
||||||
|
|
||||||
|
impl<T> YamlDecode for T
|
||||||
|
where
|
||||||
|
T: BlsCase,
|
||||||
|
{
|
||||||
|
fn yaml_decode(string: &str) -> Result<Self, Error> {
|
||||||
|
serde_yaml::from_str(string).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> LoadCase for T
|
||||||
|
where
|
||||||
|
T: BlsCase,
|
||||||
|
{
|
||||||
|
fn load_from_dir(path: &Path) -> Result<Self, Error> {
|
||||||
|
Self::yaml_decode_file(&path.join("data.yaml"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Cases<T> {
|
pub struct Cases<T> {
|
||||||
pub test_cases: Vec<T>,
|
pub test_cases: Vec<T>,
|
||||||
@ -86,6 +112,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME(michael): delete this
|
||||||
impl<T: YamlDecode> YamlDecode for Cases<T> {
|
impl<T: YamlDecode> YamlDecode for Cases<T> {
|
||||||
/// Decodes a YAML list of test cases
|
/// Decodes a YAML list of test cases
|
||||||
fn yaml_decode(yaml: &str) -> Result<Self, Error> {
|
fn yaml_decode(yaml: &str) -> Result<Self, Error> {
|
||||||
|
@ -9,11 +9,7 @@ pub struct BlsAggregatePubkeys {
|
|||||||
pub output: String,
|
pub output: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl YamlDecode for BlsAggregatePubkeys {
|
impl BlsCase for BlsAggregatePubkeys {}
|
||||||
fn yaml_decode(yaml: &str) -> Result<Self, Error> {
|
|
||||||
Ok(serde_yaml::from_str(yaml).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Case for BlsAggregatePubkeys {
|
impl Case for BlsAggregatePubkeys {
|
||||||
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
||||||
|
@ -9,11 +9,7 @@ pub struct BlsAggregateSigs {
|
|||||||
pub output: String,
|
pub output: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl YamlDecode for BlsAggregateSigs {
|
impl BlsCase for BlsAggregateSigs {}
|
||||||
fn yaml_decode(yaml: &str) -> Result<Self, Error> {
|
|
||||||
Ok(serde_yaml::from_str(yaml).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Case for BlsAggregateSigs {
|
impl Case for BlsAggregateSigs {
|
||||||
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
||||||
|
@ -15,11 +15,7 @@ pub struct BlsG2Compressed {
|
|||||||
pub output: Vec<String>,
|
pub output: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl YamlDecode for BlsG2Compressed {
|
impl BlsCase for BlsG2Compressed {}
|
||||||
fn yaml_decode(yaml: &str) -> Result<Self, Error> {
|
|
||||||
Ok(serde_yaml::from_str(yaml).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Case for BlsG2Compressed {
|
impl Case for BlsG2Compressed {
|
||||||
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
||||||
|
@ -9,11 +9,7 @@ pub struct BlsPrivToPub {
|
|||||||
pub output: String,
|
pub output: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl YamlDecode for BlsPrivToPub {
|
impl BlsCase for BlsPrivToPub {}
|
||||||
fn yaml_decode(yaml: &str) -> Result<Self, Error> {
|
|
||||||
Ok(serde_yaml::from_str(yaml).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Case for BlsPrivToPub {
|
impl Case for BlsPrivToPub {
|
||||||
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
||||||
|
@ -16,11 +16,7 @@ pub struct BlsSign {
|
|||||||
pub output: String,
|
pub output: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl YamlDecode for BlsSign {
|
impl BlsCase for BlsSign {}
|
||||||
fn yaml_decode(yaml: &str) -> Result<Self, Error> {
|
|
||||||
Ok(serde_yaml::from_str(yaml).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Case for BlsSign {
|
impl Case for BlsSign {
|
||||||
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
||||||
|
@ -3,125 +3,120 @@ use crate::case_result::compare_result;
|
|||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
use ssz::{Decode, Encode};
|
use ssz::{Decode, Encode};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::marker::PhantomData;
|
use std::fs;
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::{SignedRoot, TreeHash};
|
||||||
use types::{
|
use types::Hash256;
|
||||||
test_utils::TestRandom, Attestation, AttestationData, AttestationDataAndCustodyBit,
|
|
||||||
AttesterSlashing, BeaconBlock, BeaconBlockBody, BeaconBlockHeader, BeaconState, Checkpoint,
|
|
||||||
CompactCommittee, Crosslink, Deposit, DepositData, Eth1Data, EthSpec, Fork, Hash256,
|
|
||||||
HistoricalBatch, IndexedAttestation, PendingAttestation, ProposerSlashing, Transfer, Validator,
|
|
||||||
VoluntaryExit,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Enum variant names are used by Serde when deserializing the test YAML
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
pub enum SszStatic<E>
|
|
||||||
where
|
|
||||||
E: EthSpec,
|
|
||||||
{
|
|
||||||
Fork(SszStaticInner<Fork, E>),
|
|
||||||
Crosslink(SszStaticInner<Crosslink, E>),
|
|
||||||
Checkpoint(SszStaticInner<Checkpoint, E>),
|
|
||||||
CompactCommittee(SszStaticInner<CompactCommittee<E>, E>),
|
|
||||||
Eth1Data(SszStaticInner<Eth1Data, E>),
|
|
||||||
AttestationData(SszStaticInner<AttestationData, E>),
|
|
||||||
AttestationDataAndCustodyBit(SszStaticInner<AttestationDataAndCustodyBit, E>),
|
|
||||||
IndexedAttestation(SszStaticInner<IndexedAttestation<E>, E>),
|
|
||||||
DepositData(SszStaticInner<DepositData, E>),
|
|
||||||
BeaconBlockHeader(SszStaticInner<BeaconBlockHeader, E>),
|
|
||||||
Validator(SszStaticInner<Validator, E>),
|
|
||||||
PendingAttestation(SszStaticInner<PendingAttestation<E>, E>),
|
|
||||||
HistoricalBatch(SszStaticInner<HistoricalBatch<E>, E>),
|
|
||||||
ProposerSlashing(SszStaticInner<ProposerSlashing, E>),
|
|
||||||
AttesterSlashing(SszStaticInner<AttesterSlashing<E>, E>),
|
|
||||||
Attestation(SszStaticInner<Attestation<E>, E>),
|
|
||||||
Deposit(SszStaticInner<Deposit, E>),
|
|
||||||
VoluntaryExit(SszStaticInner<VoluntaryExit, E>),
|
|
||||||
Transfer(SszStaticInner<Transfer, E>),
|
|
||||||
BeaconBlockBody(SszStaticInner<BeaconBlockBody<E>, E>),
|
|
||||||
BeaconBlock(SszStaticInner<BeaconBlock<E>, E>),
|
|
||||||
BeaconState(SszStaticInner<BeaconState<E>, E>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct SszStaticInner<T, E>
|
struct SszStaticRoots {
|
||||||
where
|
root: String,
|
||||||
E: EthSpec,
|
signing_root: Option<String>,
|
||||||
{
|
|
||||||
pub value: T,
|
|
||||||
pub serialized: String,
|
|
||||||
pub root: String,
|
|
||||||
#[serde(skip, default)]
|
|
||||||
_phantom: PhantomData<E>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: EthSpec + serde::de::DeserializeOwned> YamlDecode for SszStatic<E> {
|
impl YamlDecode for SszStaticRoots {
|
||||||
fn yaml_decode(yaml: &str) -> Result<Self, Error> {
|
fn yaml_decode(yaml: &str) -> Result<Self, Error> {
|
||||||
serde_yaml::from_str(yaml).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))
|
Ok(serde_yaml::from_str(yaml).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: EthSpec> Case for SszStatic<E> {
|
#[derive(Debug, Clone)]
|
||||||
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
pub struct SszStatic<T> {
|
||||||
use self::SszStatic::*;
|
roots: SszStaticRoots,
|
||||||
|
serialized: Vec<u8>,
|
||||||
match *self {
|
value: T,
|
||||||
Fork(ref val) => ssz_static_test(val),
|
|
||||||
Crosslink(ref val) => ssz_static_test(val),
|
|
||||||
Checkpoint(ref val) => ssz_static_test(val),
|
|
||||||
CompactCommittee(ref val) => ssz_static_test(val),
|
|
||||||
Eth1Data(ref val) => ssz_static_test(val),
|
|
||||||
AttestationData(ref val) => ssz_static_test(val),
|
|
||||||
AttestationDataAndCustodyBit(ref val) => ssz_static_test(val),
|
|
||||||
IndexedAttestation(ref val) => ssz_static_test(val),
|
|
||||||
DepositData(ref val) => ssz_static_test(val),
|
|
||||||
BeaconBlockHeader(ref val) => ssz_static_test(val),
|
|
||||||
Validator(ref val) => ssz_static_test(val),
|
|
||||||
PendingAttestation(ref val) => ssz_static_test(val),
|
|
||||||
HistoricalBatch(ref val) => ssz_static_test(val),
|
|
||||||
ProposerSlashing(ref val) => ssz_static_test(val),
|
|
||||||
AttesterSlashing(ref val) => ssz_static_test(val),
|
|
||||||
Attestation(ref val) => ssz_static_test(val),
|
|
||||||
Deposit(ref val) => ssz_static_test(val),
|
|
||||||
VoluntaryExit(ref val) => ssz_static_test(val),
|
|
||||||
Transfer(ref val) => ssz_static_test(val),
|
|
||||||
BeaconBlockBody(ref val) => ssz_static_test(val),
|
|
||||||
BeaconBlock(ref val) => ssz_static_test(val),
|
|
||||||
BeaconState(ref val) => ssz_static_test(val),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ssz_static_test<T, E: EthSpec>(tc: &SszStaticInner<T, E>) -> Result<(), Error>
|
#[derive(Debug, Clone)]
|
||||||
where
|
pub struct SszStaticSR<T> {
|
||||||
T: Clone
|
roots: SszStaticRoots,
|
||||||
+ Decode
|
serialized: Vec<u8>,
|
||||||
+ Debug
|
value: T,
|
||||||
+ Encode
|
}
|
||||||
+ PartialEq<T>
|
|
||||||
+ serde::de::DeserializeOwned
|
// Trait alias for all deez bounds
|
||||||
+ TreeHash
|
pub trait SszStaticType:
|
||||||
+ TestRandom,
|
serde::de::DeserializeOwned + Decode + Encode + TreeHash + Clone + PartialEq + Debug
|
||||||
{
|
{
|
||||||
// Verify we can decode SSZ in the same way we can decode YAML.
|
}
|
||||||
let ssz = hex::decode(&tc.serialized[2..])
|
|
||||||
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
|
|
||||||
let expected = tc.value.clone();
|
|
||||||
let decode_result = T::from_ssz_bytes(&ssz);
|
|
||||||
compare_result(&decode_result, &Some(expected))?;
|
|
||||||
|
|
||||||
// Verify we can encode the result back into original ssz bytes
|
impl<T> SszStaticType for T where
|
||||||
let decoded = decode_result.unwrap();
|
T: serde::de::DeserializeOwned + Decode + Encode + TreeHash + Clone + PartialEq + Debug
|
||||||
let encoded_result = decoded.as_ssz_bytes();
|
{
|
||||||
compare_result::<Vec<u8>, Error>(&Ok(encoded_result), &Some(ssz))?;
|
}
|
||||||
|
|
||||||
// Verify the TreeHash root of the decoded struct matches the test.
|
fn load_from_dir<T: SszStaticType>(path: &Path) -> Result<(SszStaticRoots, Vec<u8>, T), Error> {
|
||||||
let expected_root =
|
// FIXME: set description/name
|
||||||
&hex::decode(&tc.root[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
|
let roots = SszStaticRoots::yaml_decode_file(&path.join("roots.yaml"))?;
|
||||||
let expected_root = Hash256::from_slice(&expected_root);
|
|
||||||
let tree_hash_root = Hash256::from_slice(&decoded.tree_hash_root());
|
let serialized = fs::read(&path.join("serialized.ssz")).expect("serialized.ssz exists");
|
||||||
compare_result::<Hash256, Error>(&Ok(tree_hash_root), &Some(expected_root))?;
|
|
||||||
|
let yaml = fs::read_to_string(&path.join("value.yaml")).expect("value.yaml exists");
|
||||||
|
let value =
|
||||||
|
serde_yaml::from_str(&yaml).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
|
||||||
|
|
||||||
|
Ok((roots, serialized, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SszStaticType> LoadCase for SszStatic<T> {
|
||||||
|
fn load_from_dir(path: &Path) -> Result<Self, Error> {
|
||||||
|
load_from_dir(path).map(|(roots, serialized, value)| Self {
|
||||||
|
roots,
|
||||||
|
serialized,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SszStaticType + SignedRoot> LoadCase for SszStaticSR<T> {
|
||||||
|
fn load_from_dir(path: &Path) -> Result<Self, Error> {
|
||||||
|
load_from_dir(path).map(|(roots, serialized, value)| Self {
|
||||||
|
roots,
|
||||||
|
serialized,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_serialization<T: SszStaticType>(value: &T, serialized: &[u8]) -> Result<(), Error> {
|
||||||
|
// Check serialization
|
||||||
|
let serialized_result = value.as_ssz_bytes();
|
||||||
|
compare_result::<Vec<u8>, Error>(&Ok(serialized_result), &Some(serialized.to_vec()))?;
|
||||||
|
|
||||||
|
// Check deserialization
|
||||||
|
let deserialized_result = T::from_ssz_bytes(serialized);
|
||||||
|
compare_result(&deserialized_result, &Some(value.clone()))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_tree_hash(expected_str: &str, actual_root: Vec<u8>) -> Result<(), Error> {
|
||||||
|
let expected_root = hex::decode(&expected_str[2..])
|
||||||
|
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
|
||||||
|
let expected_root = Hash256::from_slice(&expected_root);
|
||||||
|
let tree_hash_root = Hash256::from_slice(&actual_root);
|
||||||
|
compare_result::<Hash256, Error>(&Ok(tree_hash_root), &Some(expected_root))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SszStaticType> Case for SszStatic<T> {
|
||||||
|
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
||||||
|
check_serialization(&self.value, &self.serialized)?;
|
||||||
|
check_tree_hash(&self.roots.root, self.value.tree_hash_root())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SszStaticType + SignedRoot> Case for SszStaticSR<T> {
|
||||||
|
fn result(&self, _case_index: usize) -> Result<(), Error> {
|
||||||
|
check_serialization(&self.value, &self.serialized)?;
|
||||||
|
check_tree_hash(&self.roots.root, self.value.tree_hash_root())?;
|
||||||
|
check_tree_hash(
|
||||||
|
&self
|
||||||
|
.roots
|
||||||
|
.signing_root
|
||||||
|
.as_ref()
|
||||||
|
.expect("signed root exists"),
|
||||||
|
self.value.signed_root(),
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,10 +2,14 @@ use crate::case_result::CaseResult;
|
|||||||
use crate::cases::*;
|
use crate::cases::*;
|
||||||
use crate::doc_header::DocHeader;
|
use crate::doc_header::DocHeader;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::yaml_decode::{yaml_split_header_and_cases, YamlDecode};
|
use crate::yaml_decode::YamlDecode;
|
||||||
use crate::EfTest;
|
use crate::EfTest;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
use std::{fs::File, io::prelude::*, path::PathBuf};
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::prelude::*,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
use types::{MainnetEthSpec, MinimalEthSpec};
|
use types::{MainnetEthSpec, MinimalEthSpec};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -19,15 +23,13 @@ impl Doc {
|
|||||||
fn from_path(path: PathBuf) -> Self {
|
fn from_path(path: PathBuf) -> Self {
|
||||||
let mut file = File::open(path.clone()).unwrap();
|
let mut file = File::open(path.clone()).unwrap();
|
||||||
|
|
||||||
let mut yaml = String::new();
|
let mut cases_yaml = String::new();
|
||||||
file.read_to_string(&mut yaml).unwrap();
|
file.read_to_string(&mut cases_yaml).unwrap();
|
||||||
|
|
||||||
let (header_yaml, cases_yaml) = yaml_split_header_and_cases(yaml.clone());
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
header_yaml,
|
|
||||||
cases_yaml,
|
cases_yaml,
|
||||||
path,
|
path,
|
||||||
|
header_yaml: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,8 +42,6 @@ impl Doc {
|
|||||||
header.config.as_ref(),
|
header.config.as_ref(),
|
||||||
) {
|
) {
|
||||||
("ssz", "uint", _) => run_test::<SszGeneric>(self),
|
("ssz", "uint", _) => run_test::<SszGeneric>(self),
|
||||||
("ssz", "static", "minimal") => run_test::<SszStatic<MinimalEthSpec>>(self),
|
|
||||||
("ssz", "static", "mainnet") => run_test::<SszStatic<MainnetEthSpec>>(self),
|
|
||||||
("sanity", "slots", "minimal") => run_test::<SanitySlots<MinimalEthSpec>>(self),
|
("sanity", "slots", "minimal") => run_test::<SanitySlots<MinimalEthSpec>>(self),
|
||||||
// FIXME: skipped due to compact committees issue
|
// FIXME: skipped due to compact committees issue
|
||||||
("sanity", "slots", "mainnet") => vec![], // run_test::<SanitySlots<MainnetEthSpec>>(self),
|
("sanity", "slots", "mainnet") => vec![], // run_test::<SanitySlots<MainnetEthSpec>>(self),
|
||||||
@ -172,14 +172,36 @@ impl Doc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_test<T>(doc: &Doc) -> Vec<CaseResult>
|
pub fn assert_tests_pass(path: &Path, results: &[CaseResult]) {
|
||||||
|
let doc = Doc {
|
||||||
|
header_yaml: String::new(),
|
||||||
|
cases_yaml: String::new(),
|
||||||
|
path: path.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (failed, skipped_bls, skipped_known_failures) = categorize_results(results);
|
||||||
|
|
||||||
|
if failed.len() + skipped_known_failures.len() > 0 {
|
||||||
|
print_results(
|
||||||
|
&doc,
|
||||||
|
&failed,
|
||||||
|
&skipped_bls,
|
||||||
|
&skipped_known_failures,
|
||||||
|
&results,
|
||||||
|
);
|
||||||
|
if !failed.is_empty() {
|
||||||
|
panic!("Tests failed (see above)");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("Passed {} tests in {}", results.len(), path.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_test<T>(_: &Doc) -> Vec<CaseResult>
|
||||||
where
|
where
|
||||||
Cases<T>: EfTest + YamlDecode,
|
Cases<T>: EfTest + YamlDecode,
|
||||||
{
|
{
|
||||||
// Pass only the "test_cases" YAML string to `yaml_decode`.
|
panic!("FIXME(michael): delete this")
|
||||||
let test_cases: Cases<T> = Cases::yaml_decode(&doc.cases_yaml).unwrap();
|
|
||||||
|
|
||||||
test_cases.test_results()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn categorize_results(
|
pub fn categorize_results(
|
||||||
@ -208,7 +230,6 @@ pub fn print_results(
|
|||||||
skipped_known_failures: &[&CaseResult],
|
skipped_known_failures: &[&CaseResult],
|
||||||
results: &[CaseResult],
|
results: &[CaseResult],
|
||||||
) {
|
) {
|
||||||
let header: DocHeader = serde_yaml::from_str(&doc.header_yaml).unwrap();
|
|
||||||
println!("--------------------------------------------------");
|
println!("--------------------------------------------------");
|
||||||
println!(
|
println!(
|
||||||
"Test {}",
|
"Test {}",
|
||||||
@ -218,7 +239,7 @@ pub fn print_results(
|
|||||||
"Failure"
|
"Failure"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
println!("Title: {}", header.title);
|
println!("Title: TODO");
|
||||||
println!("File: {:?}", doc.path);
|
println!("File: {:?}", doc.path);
|
||||||
println!(
|
println!(
|
||||||
"{} tests, {} failed, {} skipped (known failure), {} skipped (bls), {} passed. (See below for errors)",
|
"{} tests, {} failed, {} skipped (known failure), {} skipped (bls), {} passed. (See below for errors)",
|
||||||
|
130
tests/ef_tests/src/handler.rs
Normal file
130
tests/ef_tests/src/handler.rs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
use crate::cases::{self, Case, Cases, LoadCase};
|
||||||
|
use crate::type_name::TypeName;
|
||||||
|
use crate::EfTest;
|
||||||
|
use std::fs;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tree_hash::SignedRoot;
|
||||||
|
|
||||||
|
pub trait Handler {
|
||||||
|
type Case: Case + LoadCase;
|
||||||
|
|
||||||
|
fn config_name() -> &'static str {
|
||||||
|
"general"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fork_name() -> &'static str {
|
||||||
|
"phase0"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runner_name() -> &'static str;
|
||||||
|
|
||||||
|
fn handler_name() -> &'static str;
|
||||||
|
|
||||||
|
fn run() {
|
||||||
|
let handler_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("eth2.0-spec-tests")
|
||||||
|
.join("tests")
|
||||||
|
.join(Self::config_name())
|
||||||
|
.join(Self::fork_name())
|
||||||
|
.join(Self::runner_name())
|
||||||
|
.join(Self::handler_name());
|
||||||
|
|
||||||
|
// Iterate through test suites
|
||||||
|
// TODO: parallelism
|
||||||
|
// TODO: error handling?
|
||||||
|
let test_cases = fs::read_dir(&handler_path)
|
||||||
|
.expect("open main directory")
|
||||||
|
.flat_map(|entry| {
|
||||||
|
entry
|
||||||
|
.ok()
|
||||||
|
.filter(|e| e.file_type().map(|ty| ty.is_dir()).unwrap_or(false))
|
||||||
|
})
|
||||||
|
.flat_map(|suite| fs::read_dir(suite.path()).expect("open suite dir"))
|
||||||
|
.flat_map(Result::ok)
|
||||||
|
.map(|test_case_dir| Self::Case::load_from_dir(&test_case_dir.path()).expect("loads"))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let results = Cases { test_cases }.test_results();
|
||||||
|
|
||||||
|
crate::doc::assert_tests_pass(&handler_path, &results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! bls_handler {
|
||||||
|
($runner_name: ident, $case_name:ident, $handler_name:expr) => {
|
||||||
|
pub struct $runner_name;
|
||||||
|
|
||||||
|
impl Handler for $runner_name {
|
||||||
|
type Case = cases::$case_name;
|
||||||
|
|
||||||
|
fn runner_name() -> &'static str {
|
||||||
|
"bls"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handler_name() -> &'static str {
|
||||||
|
$handler_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bls_handler!(
|
||||||
|
BlsAggregatePubkeysHandler,
|
||||||
|
BlsAggregatePubkeys,
|
||||||
|
"aggregate_pubkeys"
|
||||||
|
);
|
||||||
|
bls_handler!(BlsAggregateSigsHandler, BlsAggregateSigs, "aggregate_sigs");
|
||||||
|
bls_handler!(
|
||||||
|
BlsG2CompressedHandler,
|
||||||
|
BlsG2Compressed,
|
||||||
|
"msg_hash_compressed"
|
||||||
|
);
|
||||||
|
bls_handler!(BlsPrivToPubHandler, BlsPrivToPub, "priv_to_pub");
|
||||||
|
bls_handler!(BlsSignMsgHandler, BlsSign, "sign_msg");
|
||||||
|
|
||||||
|
/// Handler for SSZ types that do not implement `SignedRoot`.
|
||||||
|
pub struct SszStaticHandler<T, E>(PhantomData<(T, E)>);
|
||||||
|
|
||||||
|
/// Handler for SSZ types that do implement `SignedRoot`.
|
||||||
|
pub struct SszStaticSRHandler<T, E>(PhantomData<(T, E)>);
|
||||||
|
|
||||||
|
impl<T, E> Handler for SszStaticHandler<T, E>
|
||||||
|
where
|
||||||
|
T: cases::SszStaticType + TypeName,
|
||||||
|
E: TypeName,
|
||||||
|
{
|
||||||
|
type Case = cases::SszStatic<T>;
|
||||||
|
|
||||||
|
fn config_name() -> &'static str {
|
||||||
|
E::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runner_name() -> &'static str {
|
||||||
|
"ssz_static"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handler_name() -> &'static str {
|
||||||
|
T::name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> Handler for SszStaticSRHandler<T, E>
|
||||||
|
where
|
||||||
|
T: cases::SszStaticType + SignedRoot + TypeName,
|
||||||
|
E: TypeName,
|
||||||
|
{
|
||||||
|
type Case = cases::SszStaticSR<T>;
|
||||||
|
|
||||||
|
fn config_name() -> &'static str {
|
||||||
|
E::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runner_name() -> &'static str {
|
||||||
|
"ssz_static"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handler_name() -> &'static str {
|
||||||
|
T::name()
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ pub use case_result::CaseResult;
|
|||||||
pub use cases::Case;
|
pub use cases::Case;
|
||||||
pub use doc::Doc;
|
pub use doc::Doc;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
pub use handler::*;
|
||||||
pub use yaml_decode::YamlDecode;
|
pub use yaml_decode::YamlDecode;
|
||||||
|
|
||||||
mod bls_setting;
|
mod bls_setting;
|
||||||
@ -12,6 +13,8 @@ mod cases;
|
|||||||
mod doc;
|
mod doc;
|
||||||
mod doc_header;
|
mod doc_header;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod handler;
|
||||||
|
mod type_name;
|
||||||
mod yaml_decode;
|
mod yaml_decode;
|
||||||
|
|
||||||
/// Defined where an object can return the results of some test(s) adhering to the Ethereum
|
/// Defined where an object can return the results of some test(s) adhering to the Ethereum
|
||||||
|
61
tests/ef_tests/src/type_name.rs
Normal file
61
tests/ef_tests/src/type_name.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//! Mapping from types to canonical string identifiers used in testing.
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
pub trait TypeName {
|
||||||
|
fn name() -> &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeName for MinimalEthSpec {
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"minimal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeName for MainnetEthSpec {
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"mainnet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_name {
|
||||||
|
($typ:ident) => {
|
||||||
|
impl TypeName for $typ {
|
||||||
|
fn name() -> &'static str {
|
||||||
|
stringify!($typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_name_generic {
|
||||||
|
($typ:ident) => {
|
||||||
|
impl<E: EthSpec> TypeName for $typ<E> {
|
||||||
|
fn name() -> &'static str {
|
||||||
|
stringify!($typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_name_generic!(Attestation);
|
||||||
|
impl_name!(AttestationData);
|
||||||
|
impl_name!(AttestationDataAndCustodyBit);
|
||||||
|
impl_name_generic!(AttesterSlashing);
|
||||||
|
impl_name_generic!(BeaconBlock);
|
||||||
|
impl_name_generic!(BeaconBlockBody);
|
||||||
|
impl_name!(BeaconBlockHeader);
|
||||||
|
impl_name_generic!(BeaconState);
|
||||||
|
impl_name!(Checkpoint);
|
||||||
|
impl_name_generic!(CompactCommittee);
|
||||||
|
impl_name!(Crosslink);
|
||||||
|
impl_name!(Deposit);
|
||||||
|
impl_name!(DepositData);
|
||||||
|
impl_name!(Eth1Data);
|
||||||
|
impl_name!(Fork);
|
||||||
|
impl_name_generic!(HistoricalBatch);
|
||||||
|
impl_name_generic!(IndexedAttestation);
|
||||||
|
impl_name_generic!(PendingAttestation);
|
||||||
|
impl_name!(ProposerSlashing);
|
||||||
|
impl_name!(Transfer);
|
||||||
|
impl_name!(Validator);
|
||||||
|
impl_name!(VoluntaryExit);
|
@ -1,14 +1,20 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use ethereum_types::{U128, U256};
|
use ethereum_types::{U128, U256};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
use types::Fork;
|
use types::Fork;
|
||||||
|
|
||||||
mod utils;
|
|
||||||
|
|
||||||
pub use utils::*;
|
|
||||||
|
|
||||||
pub trait YamlDecode: Sized {
|
pub trait YamlDecode: Sized {
|
||||||
/// Decode an object from the test specification YAML.
|
/// Decode an object from the test specification YAML.
|
||||||
fn yaml_decode(string: &str) -> Result<Self, Error>;
|
fn yaml_decode(string: &str) -> Result<Self, Error>;
|
||||||
|
|
||||||
|
fn yaml_decode_file(path: &Path) -> Result<Self, Error> {
|
||||||
|
fs::read_to_string(path)
|
||||||
|
.map_err(|e| {
|
||||||
|
Error::FailedToParseTest(format!("Unable to load {}: {:?}", path.display(), e))
|
||||||
|
})
|
||||||
|
.and_then(|s| Self::yaml_decode(&s))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Basic types can general be decoded with the `parse` fn if they implement `str::FromStr`.
|
/// Basic types can general be decoded with the `parse` fn if they implement `str::FromStr`.
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
pub fn yaml_split_header_and_cases(mut yaml: String) -> (String, String) {
|
|
||||||
let test_cases_start = yaml.find("\ntest_cases:\n").unwrap();
|
|
||||||
// + 1 to skip the \n we used for matching.
|
|
||||||
let mut test_cases = yaml.split_off(test_cases_start + 1);
|
|
||||||
|
|
||||||
let end_of_first_line = test_cases.find('\n').unwrap();
|
|
||||||
let test_cases = test_cases.split_off(end_of_first_line + 1);
|
|
||||||
|
|
||||||
(yaml, test_cases)
|
|
||||||
}
|
|
@ -1,12 +1,20 @@
|
|||||||
use ef_tests::*;
|
use ef_tests::*;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use types::{
|
||||||
|
Attestation, AttestationData, AttestationDataAndCustodyBit, AttesterSlashing, BeaconBlock,
|
||||||
|
BeaconBlockBody, BeaconBlockHeader, BeaconState, Checkpoint, CompactCommittee, Crosslink,
|
||||||
|
Deposit, DepositData, Eth1Data, Fork, HistoricalBatch, IndexedAttestation, MainnetEthSpec,
|
||||||
|
MinimalEthSpec, PendingAttestation, ProposerSlashing, Transfer, Validator, VoluntaryExit,
|
||||||
|
};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
fn yaml_files_in_test_dir(dir: &Path) -> Vec<PathBuf> {
|
fn yaml_files_in_test_dir(dir: &Path) -> Vec<PathBuf> {
|
||||||
let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
.join("eth2.0-spec-tests")
|
.join("eth2.0-spec-tests")
|
||||||
.join("tests")
|
.join("tests")
|
||||||
|
.join("general")
|
||||||
|
.join("phase0")
|
||||||
.join(dir);
|
.join(dir);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
@ -155,12 +163,107 @@ fn sanity_slots() {
|
|||||||
#[cfg(not(feature = "fake_crypto"))]
|
#[cfg(not(feature = "fake_crypto"))]
|
||||||
fn bls() {
|
fn bls() {
|
||||||
yaml_files_in_test_dir(&Path::new("bls"))
|
yaml_files_in_test_dir(&Path::new("bls"))
|
||||||
.into_par_iter()
|
.into_iter()
|
||||||
.for_each(|file| {
|
.for_each(|file| {
|
||||||
Doc::assert_tests_pass(file);
|
Doc::assert_tests_pass(file);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(feature = "fake_crypto"))]
|
||||||
|
fn bls_aggregate_pubkeys() {
|
||||||
|
BlsAggregatePubkeysHandler::run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(feature = "fake_crypto"))]
|
||||||
|
fn bls_aggregate_sigs() {
|
||||||
|
BlsAggregateSigsHandler::run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(feature = "fake_crypto"))]
|
||||||
|
fn bls_msg_hash_g2_compressed() {
|
||||||
|
BlsG2CompressedHandler::run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(feature = "fake_crypto"))]
|
||||||
|
fn bls_priv_to_pub() {
|
||||||
|
BlsPrivToPubHandler::run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(feature = "fake_crypto"))]
|
||||||
|
fn bls_sign_msg() {
|
||||||
|
BlsSignMsgHandler::run();
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! ssz_static_test {
|
||||||
|
// Signed-root
|
||||||
|
($test_name:ident, $typ:ident$(<$generics:tt>)?, SR) => {
|
||||||
|
ssz_static_test!($test_name, SszStaticSRHandler, $typ$(<$generics>)?);
|
||||||
|
};
|
||||||
|
// Non-signed root
|
||||||
|
($test_name:ident, $typ:ident$(<$generics:tt>)?) => {
|
||||||
|
ssz_static_test!($test_name, SszStaticHandler, $typ$(<$generics>)?);
|
||||||
|
};
|
||||||
|
// Generic
|
||||||
|
($test_name:ident, $handler:ident, $typ:ident<_>) => {
|
||||||
|
ssz_static_test!(
|
||||||
|
$test_name, $handler, {
|
||||||
|
($typ<MinimalEthSpec>, MinimalEthSpec),
|
||||||
|
($typ<MainnetEthSpec>, MainnetEthSpec)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// Non-generic
|
||||||
|
($test_name:ident, $handler:ident, $typ:ident) => {
|
||||||
|
ssz_static_test!(
|
||||||
|
$test_name, $handler, {
|
||||||
|
($typ, MinimalEthSpec),
|
||||||
|
($typ, MainnetEthSpec)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// Base case
|
||||||
|
($test_name:ident, $handler:ident, { $(($typ:ty, $spec:ident)),+ }) => {
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "fake_crypto")]
|
||||||
|
fn $test_name() {
|
||||||
|
$(
|
||||||
|
$handler::<$typ, $spec>::run();
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ssz_static_test!(ssz_static_attestation, Attestation<_>, SR);
|
||||||
|
ssz_static_test!(ssz_static_attestation_data, AttestationData);
|
||||||
|
ssz_static_test!(
|
||||||
|
ssz_static_attestation_data_and_custody_bit,
|
||||||
|
AttestationDataAndCustodyBit
|
||||||
|
);
|
||||||
|
ssz_static_test!(ssz_static_attester_slashing, AttesterSlashing<_>);
|
||||||
|
ssz_static_test!(ssz_static_beacon_block, BeaconBlock<_>, SR);
|
||||||
|
ssz_static_test!(ssz_static_beacon_block_body, BeaconBlockBody<_>);
|
||||||
|
ssz_static_test!(ssz_static_beacon_block_header, BeaconBlockHeader, SR);
|
||||||
|
ssz_static_test!(ssz_static_beacon_state, BeaconState<_>);
|
||||||
|
ssz_static_test!(ssz_static_checkpoint, Checkpoint);
|
||||||
|
ssz_static_test!(ssz_static_compact_committee, CompactCommittee<_>);
|
||||||
|
ssz_static_test!(ssz_static_crosslink, Crosslink);
|
||||||
|
ssz_static_test!(ssz_static_deposit, Deposit);
|
||||||
|
ssz_static_test!(ssz_static_deposit_data, DepositData, SR);
|
||||||
|
ssz_static_test!(ssz_static_eth1_data, Eth1Data);
|
||||||
|
ssz_static_test!(ssz_static_fork, Fork);
|
||||||
|
ssz_static_test!(ssz_static_historical_batch, HistoricalBatch<_>);
|
||||||
|
ssz_static_test!(ssz_static_indexed_attestation, IndexedAttestation<_>, SR);
|
||||||
|
ssz_static_test!(ssz_static_pending_attestation, PendingAttestation<_>);
|
||||||
|
ssz_static_test!(ssz_static_proposer_slashing, ProposerSlashing);
|
||||||
|
ssz_static_test!(ssz_static_transfer, Transfer, SR);
|
||||||
|
ssz_static_test!(ssz_static_validator, Validator);
|
||||||
|
ssz_static_test!(ssz_static_voluntary_exit, VoluntaryExit, SR);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn epoch_processing_justification_and_finalization() {
|
fn epoch_processing_justification_and_finalization() {
|
||||||
yaml_files_in_test_dir(&Path::new("epoch_processing").join("justification_and_finalization"))
|
yaml_files_in_test_dir(&Path::new("epoch_processing").join("justification_and_finalization"))
|
||||||
|
Loading…
Reference in New Issue
Block a user