Skip to main content

trellis_testing/
harness_step.rs

1use std::fmt::Debug;
2
3use trellis_core::{
4    GraphResult, InputNode, OutputFrameTrace, ResourceCommandTrace, Transaction, TransactionResult,
5};
6
7use crate::{ScenarioError, ScenarioTarget, StageOperation, TrellisHarness};
8
9pub(crate) type InvariantCheck<G, C> = dyn Fn(&G, &TransactionResult<C>) -> bool + 'static;
10
11pub(crate) struct NamedInvariantCheck<G, C> {
12    pub(crate) name: String,
13    pub(crate) check: Box<InvariantCheck<G, C>>,
14}
15
16/// Builder for one harness transaction step.
17pub struct HarnessStep<'harness, G, C> {
18    harness: &'harness mut TrellisHarness<G, C>,
19    name: String,
20    operations: Vec<Box<StageOperation<C>>>,
21    expected_resource_commands: Option<Vec<ResourceCommandTrace>>,
22    expected_output_frames: Option<Vec<OutputFrameTrace>>,
23    invariant_checks: Vec<NamedInvariantCheck<G, C>>,
24}
25
26impl<'harness, G, C> HarnessStep<'harness, G, C> {
27    pub(crate) fn new(harness: &'harness mut TrellisHarness<G, C>, name: String) -> Self {
28        Self {
29            harness,
30            name,
31            operations: Vec::new(),
32            expected_resource_commands: None,
33            expected_output_frames: None,
34            invariant_checks: Vec::new(),
35        }
36    }
37}
38
39impl<'harness, G, C> HarnessStep<'harness, G, C>
40where
41    G: ScenarioTarget<C>,
42    C: Clone + Debug + PartialEq,
43{
44    /// Stages a typed canonical input write for this step.
45    pub fn input<T>(mut self, input: InputNode<T>, value: T) -> Self
46    where
47        T: Clone + PartialEq + Send + Sync + 'static,
48    {
49        self.operations
50            .push(Box::new(move |tx| tx.set_input(input, value.clone())));
51        self
52    }
53
54    /// Stages a custom operation against the transaction.
55    pub fn operation(
56        mut self,
57        operation: impl for<'tx> Fn(&mut Transaction<'tx, C>) -> GraphResult<()> + 'static,
58    ) -> Self {
59        self.operations.push(Box::new(operation));
60        self
61    }
62
63    /// Expects one resource command trace in this step.
64    pub fn expect_plan(mut self, command: ResourceCommandTrace) -> Self {
65        self.expected_resource_commands
66            .get_or_insert_with(Vec::new)
67            .push(command);
68        self
69    }
70
71    /// Expects the complete resource command trace for this step.
72    pub fn expect_plans(
73        mut self,
74        commands: impl IntoIterator<Item = ResourceCommandTrace>,
75    ) -> Self {
76        self.expected_resource_commands = Some(commands.into_iter().collect());
77        self
78    }
79
80    /// Expects one output frame trace in this step.
81    pub fn expect_output(mut self, frame: OutputFrameTrace) -> Self {
82        self.expected_output_frames
83            .get_or_insert_with(Vec::new)
84            .push(frame);
85        self
86    }
87
88    /// Expects the complete output frame trace for this step.
89    pub fn expect_outputs(mut self, frames: impl IntoIterator<Item = OutputFrameTrace>) -> Self {
90        self.expected_output_frames = Some(frames.into_iter().collect());
91        self
92    }
93
94    /// Adds a structural invariant check to record in the transaction trace.
95    pub fn check(
96        mut self,
97        name: impl Into<String>,
98        check: impl Fn(&G, &TransactionResult<C>) -> bool + 'static,
99    ) -> Self {
100        self.invariant_checks.push(NamedInvariantCheck {
101            name: name.into(),
102            check: Box::new(check),
103        });
104        self
105    }
106
107    /// Commits exactly one transaction for this step.
108    pub fn commit(self) -> Result<&'harness mut TrellisHarness<G, C>, ScenarioError> {
109        self.harness.commit_operations(
110            &self.name,
111            &self.operations,
112            &self.invariant_checks,
113            self.expected_resource_commands.as_deref(),
114            self.expected_output_frames.as_deref(),
115        )?;
116        Ok(self.harness)
117    }
118}