Skip to content

Commit

Permalink
checkpoint new ordering primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
jparr721 committed May 22, 2024
1 parent 4b5478c commit 8817bb2
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 35 deletions.
152 changes: 123 additions & 29 deletions crates/testing/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,141 @@ use hotshot_task_impls::events::HotShotEvent;
use hotshot_types::traits::node_implementation::NodeType;
use rand::seq::SliceRandom;

use crate::predicates::{Predicate, PredicateResult};
use crate::predicates::{
event::{all_predicates, EventPredicate},
Predicate, PredicateResult,
};

pub const RECV_TIMEOUT: Duration = Duration::from_millis(250);

pub struct TestScriptStage<TYPES: NodeType, S: TaskState<Event = Arc<HotShotEvent<TYPES>>>> {
pub inputs: Vec<HotShotEvent<TYPES>>,
pub outputs: Vec<Box<dyn Predicate<Arc<HotShotEvent<TYPES>>>>>,
pub asserts: Vec<Box<dyn Predicate<S>>>,
/// Ordered inputs sometimes need to happen before or after the unordered
/// inputs have occurred in the test. So we need a flexible way to specify
/// this.
#[derive(Clone, Copy)]
pub enum TestScriptStageOrderedInputSequence {
/// All ordered events occur before the unordered events.
Before,

/// All ordered events occur after the unordered events.
After,
}

impl<TYPES: NodeType, S: TaskState<Event = Arc<HotShotEvent<TYPES>>>> TestScriptStage<TYPES, S> {
/// A mixed test input is one which coalesces the ordered and unordered values
/// into one homogenous output set given some `sequence` ordering.
#[derive(Clone)]
pub struct MixedTestInput<T: std::fmt::Debug> {
/// The ordered inputs. These will be processed in the exact order
/// that they were provided.
ordered: Vec<T>,

/// The unordered inputs. These will be arbitrarily permuted on each
/// test run according to [`rand::thread_rng`].
unordered: Vec<T>,

/// The sequence specifies the placement of the events.
sequence: TestScriptStageOrderedInputSequence,
}

impl<T: std::fmt::Debug> MixedTestInput<T> {
pub fn new(
inputs: Vec<HotShotEvent<TYPES>>,
outputs: Vec<Box<dyn Predicate<Arc<HotShotEvent<TYPES>>>>>,
asserts: Vec<Box<dyn Predicate<S>>>,
ordered: Vec<T>,
unordered: Vec<T>,
sequence: TestScriptStageOrderedInputSequence,
) -> Self {
Self {
inputs,
outputs,
asserts,
ordered,
unordered,
sequence,
}
}

pub fn from_inputs_and_outputs(
inputs: Vec<HotShotEvent<TYPES>>,
outputs: Vec<Box<dyn Predicate<Arc<HotShotEvent<TYPES>>>>>,
) -> Self {
Self {
inputs,
outputs,
asserts: Vec::new(),
pub fn flatten(&self) -> Vec<&T> {
match self.sequence {
TestScriptStageOrderedInputSequence::Before => {
self.ordered.iter().chain(self.unordered.iter()).collect()
}
TestScriptStageOrderedInputSequence::After => {
self.unordered.iter().chain(self.ordered.iter()).collect()
}
}
}
}

/// This type represents the input/output to a script stage. It can be all
/// `ordered` events, where the test will run the events in the exact order
/// that they were applied, or it can be all `unordered` where the test will
/// randomly permute the order of the inputs arbitrarily on each test run, or
/// a mix of the two, provided that a `sequence` rule is applied.
#[derive(Clone)]
pub enum TestScriptStageValues<T: std::fmt::Debug> {
/// The ordered inputs. These will be processed in the exact order
/// that they were provided.
Ordered(Vec<T>),

/// The unordered inputs. These will be arbitrarily permuted on each
/// test run according to [`rand::thread_rng`].
Unordered(Vec<T>),

/// The mixed inputs. The unordered will be arbitrarily permuted on each
/// test run according to [`rand::thread_rng`], and the ordered will be
/// executed in sequence according to
/// [`TestScriptStageOrderedInputSequence`].
Mixed(MixedTestInput<T>),
}

impl<T: std::fmt::Debug> TestScriptStageValues<T> {
/// Create the test script input from an `ordered` sequence of events.
/// This method does not require the specification of a sequence ordering.
pub fn from_ordered(ordered: Vec<T>) -> Self {
TestScriptStageValues::Ordered(ordered)
}

/// Create the test script input from an `unordered` sequence of events.
/// This method does not require the specification of a sequence ordering.
/// In all unordered inputs, we permute once to avoid non-determinism
/// in the `values` method.
pub fn from_unordered(unordered: Vec<T>) -> Self {
let mut unordered = unordered;
unordered.shuffle(&mut rand::thread_rng());
TestScriptStageValues::Unordered(unordered)
}

/// Create a test script input from a `mixed` sequence of events.
/// This method requires the specification of a sequence ordering.
/// In all unordered inputs, we permute once to avoid non-determinism
/// in the `values` method.
pub fn from_mixed(mixed: MixedTestInput<T>) -> Self {
let mut mixed = mixed;
mixed.unordered.shuffle(&mut rand::thread_rng());
TestScriptStageValues::Mixed(mixed)
}

/// Obtain the inputs as a flat sequence with randomized ordering applied
/// if applicable.
pub fn values(&self) -> Vec<&T> {
match self {
Self::Ordered(values) => values.iter().collect(),
Self::Unordered(values) => values.iter().collect(),
Self::Mixed(mixed_values) => mixed_values.flatten(),
}
}
}

/// A helper method to wrap the output type within tests to guarantee that the outputs are evaluated
/// irrespective of ordering.
pub fn unordered_outputs<TYPES: NodeType>(
unordered: Vec<Box<EventPredicate<TYPES>>>,
) -> TestScriptStageValues<Box<dyn Predicate<Arc<HotShotEvent<TYPES>>>>> {
let predicate = all_predicates(unordered);
TestScriptStageValues::Unordered(vec![predicate])
}

pub struct TestScriptStage<TYPES: NodeType, S: TaskState<Event = Arc<HotShotEvent<TYPES>>>> {
pub inputs: TestScriptStageValues<HotShotEvent<TYPES>>,
pub outputs: TestScriptStageValues<Box<dyn Predicate<Arc<HotShotEvent<TYPES>>>>>,
pub asserts: Vec<Box<dyn Predicate<S>>>,
}

/// A `TestScript` is a sequence of triples (input sequence, output sequence, assertions).
pub struct TestScript<TYPES: NodeType, S: TaskState<Event = Arc<HotShotEvent<TYPES>>>> {
stages: Vec<TestScriptStage<TYPES, S>>,
Expand All @@ -55,12 +155,6 @@ impl<TYPES: NodeType, S: TaskState<Event = Arc<HotShotEvent<TYPES>>>> TestScript
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut TestScriptStage<TYPES, S>> {
self.stages.iter_mut()
}

pub fn randomize(&mut self) {
for stage in self.iter_mut() {
stage.inputs.shuffle(&mut rand::thread_rng());
}
}
}

impl<TYPES: NodeType, S: TaskState<Event = Arc<HotShotEvent<TYPES>>>>
Expand Down Expand Up @@ -152,7 +246,7 @@ pub async fn run_test_script<
TYPES: NodeType,
{
let mut script: TestScript<TYPES, S> = script.into();
script.randomize();

let registry = Arc::new(TaskRegistry::default());

let (to_task, mut from_test) = broadcast(1024);
Expand All @@ -162,7 +256,7 @@ pub async fn run_test_script<

for (stage_number, stage) in script.iter_mut().enumerate() {
tracing::debug!("Beginning test stage {}", stage_number);
for input in &stage.inputs {
for input in stage.inputs.values() {
to_task
.broadcast(input.clone().into())
.await
Expand All @@ -179,7 +273,7 @@ pub async fn run_test_script<
while from_test.try_recv().is_ok() {}
}

for assert in &mut stage.outputs {
for assert in stage.outputs.values() {
let mut result = PredicateResult::Incomplete;

while let Ok(Ok(received_output)) =
Expand Down
14 changes: 8 additions & 6 deletions crates/testing/tests/tests_1/consensus_task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use sha2::Digest;
#[cfg_attr(async_executor_impl = "async-std", async_std::test)]
async fn test_consensus_task() {
use hotshot_example_types::block_types::TestMetadata;
use hotshot_testing::script::{unordered_outputs, TestScriptStageValues};
use hotshot_types::data::null_block;

async_compatibility_layer::logging::setup_logging();
Expand Down Expand Up @@ -66,18 +67,19 @@ async fn test_consensus_task() {
}

// Run view 1 (the genesis stage).
let view_1 = TestScriptStage::from_inputs_and_outputs(
vec![
let view_1 = TestScriptStage {
inputs: TestScriptStageValues::from_unordered(vec![
QuorumProposalRecv(proposals[0].clone(), leaders[0]),
DaCertificateRecv(dacs[0].clone()),
VidShareRecv(vid_share(&vids[0].0, handle.public_key())),
],
vec![
]),
outputs: unordered_outputs(vec![
exact(ViewChange(ViewNumber::new(1))),
quorum_proposal_validated(),
exact(QuorumVoteSend(votes[0].clone())),
],
);
]),
asserts: vec![],
};

let cert = proposals[1].data.justify_qc.clone();
let builder_commitment = BuilderCommitment::from_raw_digest(sha2::Sha256::new().finalize());
Expand Down

0 comments on commit 8817bb2

Please sign in to comment.