Skip to main content

rust_supervisor/test_support/
factory.rs

1//! Deterministic helpers for supervisor tests.
2//!
3//! The module provides small reusable fixtures for event collection, paused
4//! time, fake task outcomes, and deterministic jitter.
5
6use crate::error::types::TaskFailure;
7use crate::event::payload::{SupervisorEvent, What, Where};
8use crate::event::time::{CorrelationId, EventSequence, EventSequenceSource, EventTime, When};
9use crate::id::types::{Attempt, ChildId, Generation, SupervisorPath};
10use serde::{Deserialize, Serialize};
11use uuid::Uuid;
12
13/// Paused time source for deterministic tests.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15pub struct PausedTime {
16    /// Wall-clock time in nanoseconds since the Unix epoch.
17    pub unix_nanos: u128,
18    /// Monotonic time in nanoseconds.
19    pub monotonic_nanos: u128,
20    /// Supervisor uptime in milliseconds.
21    pub uptime_ms: u64,
22}
23
24impl PausedTime {
25    /// Creates a paused time source.
26    ///
27    /// # Arguments
28    ///
29    /// - `unix_nanos`: Wall-clock timestamp in nanoseconds.
30    /// - `monotonic_nanos`: Monotonic timestamp in nanoseconds.
31    /// - `uptime_ms`: Supervisor uptime in milliseconds.
32    ///
33    /// # Returns
34    ///
35    /// Returns a [`PausedTime`] value.
36    ///
37    /// # Examples
38    ///
39    /// ```
40    /// let time = rust_supervisor::test_support::factory::PausedTime::new(1, 2, 3);
41    /// assert_eq!(time.uptime_ms, 3);
42    /// ```
43    pub fn new(unix_nanos: u128, monotonic_nanos: u128, uptime_ms: u64) -> Self {
44        Self {
45            unix_nanos,
46            monotonic_nanos,
47            uptime_ms,
48        }
49    }
50
51    /// Creates deterministic event time.
52    ///
53    /// # Arguments
54    ///
55    /// - `generation`: Child generation for the event.
56    /// - `attempt`: Child attempt for the event.
57    ///
58    /// # Returns
59    ///
60    /// Returns an [`EventTime`] value.
61    pub fn event_time(&self, generation: Generation, attempt: Attempt) -> EventTime {
62        EventTime::deterministic(
63            self.unix_nanos,
64            self.monotonic_nanos,
65            self.uptime_ms,
66            generation,
67            attempt,
68        )
69    }
70}
71
72/// Deterministic jitter helper for backoff tests.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
74pub struct DeterministicJitter {
75    /// Percentage points applied to the base delay.
76    pub percent: i64,
77}
78
79impl DeterministicJitter {
80    /// Creates a deterministic jitter source.
81    ///
82    /// # Arguments
83    ///
84    /// - `percent`: Signed percentage applied to the base delay.
85    ///
86    /// # Returns
87    ///
88    /// Returns a [`DeterministicJitter`] value.
89    pub fn new(percent: i64) -> Self {
90        Self { percent }
91    }
92
93    /// Applies jitter to a millisecond delay.
94    ///
95    /// # Arguments
96    ///
97    /// - `base_ms`: Base delay in milliseconds.
98    ///
99    /// # Returns
100    ///
101    /// Returns the adjusted delay in milliseconds.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// let jitter = rust_supervisor::test_support::factory::DeterministicJitter::new(10);
107    /// assert_eq!(jitter.apply_ms(100), 110);
108    /// ```
109    pub fn apply_ms(&self, base_ms: u64) -> u64 {
110        let base = i128::from(base_ms);
111        let delta = base.saturating_mul(i128::from(self.percent)) / 100;
112        base.saturating_add(delta).max(0) as u64
113    }
114}
115
116/// Fake task outcome for tests that do not need a real runtime.
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118pub enum FakeTaskOutcome {
119    /// Task completes successfully.
120    Complete,
121    /// Task is cancelled.
122    Cancel,
123    /// Task returns a typed failure.
124    Fail {
125        /// Failure payload returned by the task.
126        failure: TaskFailure,
127    },
128}
129
130/// Fake task factory that returns deterministic outcomes in order.
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132pub struct FakeTaskFactory {
133    /// Ordered outcomes returned by attempts.
134    pub outcomes: Vec<FakeTaskOutcome>,
135    /// Next outcome index.
136    pub next_index: usize,
137}
138
139impl FakeTaskFactory {
140    /// Creates a fake task factory.
141    ///
142    /// # Arguments
143    ///
144    /// - `outcomes`: Ordered outcomes returned by attempts.
145    ///
146    /// # Returns
147    ///
148    /// Returns a [`FakeTaskFactory`].
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// let mut factory = rust_supervisor::test_support::factory::FakeTaskFactory::new(vec![
154    ///     rust_supervisor::test_support::factory::FakeTaskOutcome::Complete,
155    /// ]);
156    /// assert!(matches!(
157    ///     factory.next_outcome(),
158    ///     rust_supervisor::test_support::factory::FakeTaskOutcome::Complete
159    /// ));
160    /// ```
161    pub fn new(outcomes: Vec<FakeTaskOutcome>) -> Self {
162        Self {
163            outcomes,
164            next_index: 0,
165        }
166    }
167
168    /// Returns the next deterministic outcome.
169    ///
170    /// # Arguments
171    ///
172    /// This function has no arguments.
173    ///
174    /// # Returns
175    ///
176    /// Returns the next [`FakeTaskOutcome`], or `Complete` after configured
177    /// outcomes are exhausted.
178    pub fn next_outcome(&mut self) -> FakeTaskOutcome {
179        let outcome = self
180            .outcomes
181            .get(self.next_index)
182            .cloned()
183            .unwrap_or(FakeTaskOutcome::Complete);
184        self.next_index = self.next_index.saturating_add(1);
185        outcome
186    }
187}
188
189/// Collector that stores supervisor events in memory.
190#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
191pub struct EventCollector {
192    /// Events collected in receive order.
193    pub events: Vec<SupervisorEvent>,
194}
195
196impl EventCollector {
197    /// Creates an empty collector.
198    ///
199    /// # Arguments
200    ///
201    /// This function has no arguments.
202    ///
203    /// # Returns
204    ///
205    /// Returns a new [`EventCollector`].
206    pub fn new() -> Self {
207        Self::default()
208    }
209
210    /// Pushes one event into the collector.
211    ///
212    /// # Arguments
213    ///
214    /// - `event`: Event to store.
215    ///
216    /// # Returns
217    ///
218    /// This function does not return a value.
219    pub fn push(&mut self, event: SupervisorEvent) {
220        self.events.push(event);
221    }
222
223    /// Returns collected event names.
224    ///
225    /// # Arguments
226    ///
227    /// This function has no arguments.
228    ///
229    /// # Returns
230    ///
231    /// Returns event names in receive order.
232    pub fn event_names(&self) -> Vec<&'static str> {
233        self.events.iter().map(|event| event.what.name()).collect()
234    }
235}
236
237/// Fixture that builds deterministic lifecycle events.
238#[derive(Debug)]
239pub struct EventFixture {
240    /// Paused time used for every event.
241    pub paused_time: PausedTime,
242    /// Sequence source used by the fixture.
243    pub sequences: EventSequenceSource,
244    /// Correlation identifier used by the fixture.
245    pub correlation_id: CorrelationId,
246    /// Configuration version attached to events.
247    pub config_version: u64,
248}
249
250impl EventFixture {
251    /// Creates an event fixture.
252    ///
253    /// # Arguments
254    ///
255    /// - `paused_time`: Time source for deterministic events.
256    /// - `config_version`: Configuration version attached to events.
257    ///
258    /// # Returns
259    ///
260    /// Returns an [`EventFixture`].
261    pub fn new(paused_time: PausedTime, config_version: u64) -> Self {
262        Self {
263            paused_time,
264            sequences: EventSequenceSource::new(),
265            correlation_id: CorrelationId::from_uuid(Uuid::nil()),
266            config_version,
267        }
268    }
269
270    /// Builds a deterministic event for a child.
271    ///
272    /// # Arguments
273    ///
274    /// - `child_id`: Child identifier attached to the event.
275    /// - `child_name`: Child name attached to the event.
276    /// - `what`: Event payload.
277    ///
278    /// # Returns
279    ///
280    /// Returns a [`SupervisorEvent`].
281    pub fn child_event(
282        &self,
283        child_id: ChildId,
284        child_name: impl Into<String>,
285        what: What,
286    ) -> SupervisorEvent {
287        let path = SupervisorPath::root().join(child_id.to_string());
288        let location = Where::new(path.clone()).with_child(child_id, child_name);
289        SupervisorEvent::new(
290            When::new(
291                self.paused_time
292                    .event_time(Generation::initial(), Attempt::first()),
293            ),
294            location,
295            what,
296            self.sequences.next(),
297            self.correlation_id,
298            self.config_version,
299        )
300    }
301
302    /// Builds an event sequence value.
303    ///
304    /// # Arguments
305    ///
306    /// - `value`: Sequence value.
307    ///
308    /// # Returns
309    ///
310    /// Returns an [`EventSequence`].
311    pub fn sequence(value: u64) -> EventSequence {
312        EventSequence::new(value)
313    }
314}