Skip to content

Commit 9a13d13

Browse files
authored
test/doc: Atomic (#67)
1 parent 9fd41c1 commit 9a13d13

File tree

2 files changed

+166
-3
lines changed

2 files changed

+166
-3
lines changed

src/steps/combinator/atomic.rs

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,37 @@
11
use {super::*, crate::platform::types::BuiltPayload};
22

33
combinator!(
4-
/// TODO
4+
/// The [`Atomic`] combinator executes a sequence of steps with atomic
5+
/// (all-or-nothing) semantics. If any step fails or breaks, the entire
6+
/// sequence rolls back to the initial checkpoint state.
7+
///
8+
/// # Execution Semantics
9+
///
10+
/// - If all steps return [`ControlFlow::Ok`], the final checkpoint is returned
11+
/// - If any step returns [`ControlFlow::Break`], the initial checkpoint is
12+
/// returned
13+
/// - If any step returns [`ControlFlow::Fail`], the initial checkpoint is
14+
/// returned
15+
/// - The checkpoint is saved at the beginning and restored if any step doesn't
16+
/// return Ok
17+
///
18+
/// At any point, if the deadline is reached, execution stops and returns
19+
/// [`ControlFlow::Break`] with the initial checkpoint.
20+
///
21+
/// # Example
22+
///
23+
/// ```rust
24+
/// use rblib::prelude::*;
25+
///
26+
/// # fn example<P: Platform>(
27+
/// # step1: impl Step<P>,
28+
/// # step2: impl Step<P>,
29+
/// # step3: impl Step<P>
30+
/// # ) {
31+
/// let atomic = Atomic::of(step1).and(step2).and(step3);
32+
/// let atomic = atomic!(step1, step2, step3);
33+
/// # }
34+
/// ```
535
, Atomic, and
636
);
737

@@ -74,3 +104,126 @@ macro_rules! atomic {
74104
c
75105
}};
76106
}
107+
108+
#[cfg(test)]
109+
mod tests {
110+
use {
111+
super::*,
112+
crate::{
113+
alloy::network::TransactionBuilder,
114+
platform::{Ethereum, Optimism},
115+
steps::{CombinatorStep, RemoveRevertedTransactions},
116+
test_utils::*,
117+
},
118+
futures::StreamExt,
119+
};
120+
121+
fake_step!(OkEvent2, emit_events, noop_ok);
122+
123+
#[rblib_test(Ethereum, Optimism)]
124+
async fn atomic_ok_one_step<P: TestablePlatform>() {
125+
let atomic = Atomic::of(OkWithEventStep);
126+
127+
let step = OneStep::<P>::new(atomic);
128+
let mut event_sub = step.subscribe::<StringEvent>();
129+
130+
let output = step.run().await.unwrap();
131+
assert!(output.is_ok());
132+
133+
assert_eq!(
134+
event_sub.next().await,
135+
Some(StringEvent("OkWithEventStep: before_job".to_string()))
136+
);
137+
assert_eq!(
138+
event_sub.next().await,
139+
Some(StringEvent("OkWithEventStep: step".to_string()))
140+
);
141+
assert_eq!(
142+
event_sub.next().await,
143+
Some(StringEvent("OkWithEventStep: after_job".to_string()))
144+
);
145+
}
146+
147+
#[rblib_test(Ethereum, Optimism)]
148+
async fn atomic_ok_execute_in_order<P: TestablePlatform>() {
149+
let atomic = Atomic::of(OkWithEventStep).and(OkEvent2);
150+
151+
let step = OneStep::<P>::new(atomic);
152+
let mut event_sub = step.subscribe::<StringEvent>();
153+
154+
let output = step.run().await.unwrap();
155+
assert!(output.is_ok());
156+
157+
assert_eq!(
158+
event_sub.next().await,
159+
Some(StringEvent("OkWithEventStep: before_job".to_string()))
160+
);
161+
assert_eq!(
162+
event_sub.next().await,
163+
Some(StringEvent("OkEvent2: before_job".to_string()))
164+
);
165+
166+
assert_eq!(
167+
event_sub.next().await,
168+
Some(StringEvent("OkWithEventStep: step".to_string()))
169+
);
170+
assert_eq!(
171+
event_sub.next().await,
172+
Some(StringEvent("OkEvent2: step".to_string()))
173+
);
174+
175+
assert_eq!(
176+
event_sub.next().await,
177+
Some(StringEvent("OkWithEventStep: after_job".to_string()))
178+
);
179+
assert_eq!(
180+
event_sub.next().await,
181+
Some(StringEvent("OkEvent2: after_job".to_string()))
182+
);
183+
}
184+
185+
#[rblib_test(Ethereum, Optimism)]
186+
async fn atomic_break_reverts_to_initial<P: TestablePlatform>() {
187+
let atomic =
188+
Atomic::of(RemoveRevertedTransactions::default()).and(AlwaysBreakStep);
189+
190+
let output = OneStep::<P>::new(atomic)
191+
.with_payload_tx(|tx| tx.reverting().with_default_signer().with_nonce(0))
192+
.run()
193+
.await
194+
.unwrap();
195+
196+
let ControlFlow::Ok(payload) = output else {
197+
panic!("Expected Ok payload (reverted), got: {output:?}");
198+
};
199+
200+
// The reverting tx from the initial payload should not be removed
201+
assert_eq!(payload.history().transactions().count(), 1);
202+
}
203+
204+
#[rblib_test(Ethereum, Optimism)]
205+
async fn atomic_fail_reverts_to_initial<P: TestablePlatform>() {
206+
let atomic =
207+
Atomic::of(RemoveRevertedTransactions::default()).and(AlwaysFailStep);
208+
209+
let output = OneStep::<P>::new(atomic)
210+
.with_payload_tx(|tx| tx.reverting().with_default_signer().with_nonce(0))
211+
.run()
212+
.await
213+
.unwrap();
214+
215+
let ControlFlow::Ok(payload) = output else {
216+
panic!("Expected Ok payload (reverted), got: {output:?}");
217+
};
218+
219+
// The reverting tx from the initial payload should not be removed
220+
assert_eq!(payload.history().transactions().count(), 1);
221+
}
222+
223+
#[rblib_test(Ethereum, Optimism)]
224+
async fn atomic_macro<P: TestablePlatform>() {
225+
let atomic = atomic!(AlwaysOkStep, AlwaysOkStep, AlwaysOkStep);
226+
let output = OneStep::<P>::new(atomic).run().await.unwrap();
227+
assert!(output.is_ok());
228+
}
229+
}

src/steps/combinator/chain.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ combinator!(
1515
/// - The final result is either the last successful checkpoint or the first
1616
/// non-Ok control flow
1717
///
18+
/// At any point, if the deadline is reached, execution stops and returns
19+
/// [`ControlFlow::Break`] with the last successful checkpoint.
20+
///
1821
/// # Example
1922
///
2023
/// ```rust
@@ -95,9 +98,9 @@ impl<P: Platform> Step<P> for Chain<P> {
9598
#[macro_export]
9699
macro_rules! chain {
97100
($first:expr $(, $rest:expr)* $(,)?) => {{
98-
let mut c = Atomic::of($first);
101+
let mut c = Chain::of($first);
99102
$(
100-
c = c.and($rest);
103+
c = c.then($rest);
101104
)*
102105
c
103106
}};
@@ -220,4 +223,11 @@ mod tests {
220223
Some(StringEvent("OkWithEventStep: after_job".to_string()))
221224
);
222225
}
226+
227+
#[rblib_test(Ethereum, Optimism)]
228+
async fn chain_macro<P: TestablePlatform>() {
229+
let chain = chain!(AlwaysOkStep, AlwaysOkStep, AlwaysOkStep);
230+
let output = OneStep::<P>::new(chain).run().await.unwrap();
231+
assert!(output.is_ok());
232+
}
223233
}

0 commit comments

Comments
 (0)