diff --git a/Code/itf/tests/votekeeper.rs b/Code/itf/tests/votekeeper.rs index d94eccfe8..17e451ad2 100644 --- a/Code/itf/tests/votekeeper.rs +++ b/Code/itf/tests/votekeeper.rs @@ -1,12 +1,13 @@ use std::collections::HashMap; +use std::ops::Deref; use std::path::PathBuf; use rand::rngs::StdRng; use rand::SeedableRng; -use malachite_common::Round; -use malachite_itf::votekeeper::State as ItfState; -use malachite_itf::votekeeper::Value as ItfValue; +use malachite_common::{Context, Round, Value}; +use malachite_itf::votekeeper::{State as ItfState, Value as ItfValue}; +use malachite_itf::TraceRunner; use malachite_test::{Address, PrivateKey, TestContext, ValueId, Vote}; use malachite_vote::keeper::{Message, VoteKeeper}; use malachite_vote::Weight; @@ -14,15 +15,18 @@ use malachite_vote::Weight; use itf::Itf; use rstest::{fixture, rstest}; +const ADDRESSES: [&str; 3] = ["alice", "bob", "john"]; + // TODO: move to itf-rs repo -fn from_itf(bigint: Itf) -> Option +fn from_itf(bigint: &Itf) -> Option where U: TryFrom, + T: Clone, { - bigint.value().try_into().ok() + bigint.deref().clone().try_into().ok() } -fn value_from_model(value: ItfValue) -> Option { +fn value_from_model(value: &ItfValue) -> Option { match value.as_str() { "nil" => None, "proposal" => Some(0.into()), @@ -33,46 +37,36 @@ fn value_from_model(value: ItfValue) -> Option { } } -#[fixture] -#[once] -fn model_address_map() -> HashMap { - let mut rng = StdRng::seed_from_u64(0x42); - - // build mapping from model addresses to real addresses - let valid_model_addresses = ["alice", "bob", "john"]; - valid_model_addresses - .iter() - .map(|&name| { - let pk = PrivateKey::generate(&mut rng).public_key(); - (name.into(), Address::from_public_key(&pk)) - }) - .collect() +struct VoteKeeperRunner { + address_map: HashMap, } -#[rstest] -fn test_itf( - #[files("tests/fixtures/votekeeper/*.json")] json_fixture: PathBuf, - model_address_map: &HashMap, -) { - println!("Parsing {json_fixture:?}"); +impl TraceRunner for VoteKeeperRunner { + type ActualState = VoteKeeper; + type Result = Option::Value as Value>::Id>>; - let json = std::fs::read_to_string(&json_fixture).unwrap(); - let trace = itf::trace_from_str::(&json).unwrap(); - - // Obtain the initial total_weight from the first state in the model. - let bookkeper = trace.states[0].value.bookkeeper.clone(); - let total_weight: Weight = from_itf(bookkeper.total_weight).unwrap(); + type ExpectedState = ItfState; + type Error = (); - let mut keeper: VoteKeeper = VoteKeeper::new(total_weight); - - for state in &trace.states[1..] { - let state = state.clone().value; + fn init( + &mut self, + expected_state: &Self::ExpectedState, + ) -> Result { + // Obtain the initial total_weight from the first state in the model. + let total_weight: Weight = from_itf(&expected_state.bookkeeper.total_weight).unwrap(); + Ok(VoteKeeper::new(total_weight)) + } + fn step( + &mut self, + state: &mut Self::ActualState, + expected_state: &Self::ExpectedState, + ) -> Result { // Build step to execute. - let (input_vote, weight) = state.weighted_vote.value(); - let round = Round::new(from_itf(input_vote.round).unwrap()); - let value = value_from_model(input_vote.value); - let address = model_address_map.get(input_vote.address.as_str()).unwrap(); + let (input_vote, weight) = expected_state.weighted_vote.deref(); + let round = Round::new(from_itf(&input_vote.round).unwrap()); + let value = value_from_model(&input_vote.value); + let address = self.address_map.get(input_vote.address.as_str()).unwrap(); let vote = match input_vote.typ.as_str() { "Prevote" => Vote::new_prevote(round, value, *address), "Precommit" => Vote::new_precommit(round, value, *address), @@ -80,38 +74,100 @@ fn test_itf( }; let weight: Weight = from_itf(weight).unwrap(); println!( - "🟢 step: vote={:?}, round={:?}, value={:?}, address={:?}, weight={:?}", + "🔵 step: vote={:?}, round={:?}, value={:?}, address={:?}, weight={:?}", input_vote.typ, round, value, input_vote.address, weight ); // Execute step. - let result = keeper.apply_vote(vote.clone(), weight); + Ok(state.apply_vote(vote.clone(), weight)) + } + fn result_invariant( + &self, + _state: &Self::ActualState, + result: &Self::Result, + expected_state: &Self::ExpectedState, + ) -> Result { // Get expected result. - let model_result = state.last_emitted; + let expected_result = &expected_state.last_emitted; println!( "🟣 result: model={:?}({:?}), code={:?}", - model_result.name, model_result.value, result + expected_result.name, expected_result.value, result ); - // Check result against expected result. match result { Some(result) => match result { Message::PolkaValue(value) => { - assert_eq!(model_result.name, "PolkaValue"); - assert_eq!(value_from_model(model_result.value), Some(value)); + assert_eq!(expected_result.name, "PolkaValue"); + assert_eq!( + value_from_model(&expected_result.value).as_ref(), + Some(value) + ); } Message::PrecommitValue(value) => { - assert_eq!(model_result.name, "PrecommitValue"); - assert_eq!(value_from_model(model_result.value), Some(value)); + assert_eq!(expected_result.name, "PrecommitValue"); + assert_eq!( + value_from_model(&expected_result.value).as_ref(), + Some(value) + ); } Message::SkipRound(round) => { - assert_eq!(model_result.name, "SkipRound"); - assert_eq!(Round::new(from_itf(model_result.round).unwrap()), round); + assert_eq!(expected_result.name, "SkipRound"); + assert_eq!( + &Round::new(from_itf(&expected_result.round).unwrap()), + round + ); } - msg => assert_eq!(model_result.name, format!("{:?}", msg)), + msg => assert_eq!(expected_result.name, format!("{msg:?}")), }, - None => assert_eq!(model_result.name, "None"), + None => assert_eq!(expected_result.name, "None"), } + Ok(true) + } + + fn state_invariant( + &self, + _actual_state: &Self::ActualState, + _expected_state: &Self::ExpectedState, + ) -> Result { + Ok(true) } } + +#[fixture] +fn vote_keeper_runner() -> VoteKeeperRunner { + let mut rng = StdRng::seed_from_u64(0x42); + + // build mapping from model addresses to real addresses + VoteKeeperRunner { + address_map: ADDRESSES + .iter() + .map(|&name| { + let pk = PrivateKey::generate(&mut rng).public_key(); + (name.into(), Address::from_public_key(&pk)) + }) + .collect(), + } +} + +#[rstest] +fn test_itf( + #[files("tests/fixtures/votekeeper/*.json")] json_fixture: PathBuf, + mut vote_keeper_runner: VoteKeeperRunner, +) { + println!("Parsing {json_fixture:?}"); + + let json = std::fs::read_to_string(&json_fixture).unwrap(); + let trace = itf::trace_from_str::(&json).unwrap(); + + vote_keeper_runner + .test( + trace + .states + .into_iter() + .map(|s| s.value) + .collect::>() + .as_slice(), + ) + .unwrap(); +}