Skip to main content

apm_core/wrapper/builtin/
mock_random.rs

1use crate::config::resolve_outcome;
2use super::{load_transitions_with_outcomes, is_impl_mode, happy_script, sad_script, seed_from_ctx, write_and_spawn_script};
3use crate::wrapper::{Wrapper, WrapperContext};
4
5pub struct MockRandomWrapper;
6
7pub(crate) fn pick_transition_idx(seed: u64, count: usize) -> usize {
8    (seed as usize) % count
9}
10
11impl Wrapper for MockRandomWrapper {
12    fn spawn(&self, ctx: &WrapperContext) -> anyhow::Result<std::process::Child> {
13        let transitions = load_transitions_with_outcomes(ctx)?;
14        if transitions.is_empty() {
15            anyhow::bail!(
16                "mock-random: no valid transitions from state '{}'",
17                ctx.current_state
18            );
19        }
20        let seed = seed_from_ctx(ctx);
21        let idx = pick_transition_idx(seed, transitions.len());
22        let chosen = &transitions[idx];
23        let outcome = resolve_outcome(&chosen.0, &chosen.1);
24        let target = chosen.0.to.clone();
25        let script = if outcome == "success" {
26            happy_script(&ctx.ticket_id, &target, is_impl_mode(&transitions))
27        } else {
28            sad_script(&ctx.ticket_id, &target)
29        };
30        write_and_spawn_script("random", &script, ctx)
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37
38    #[test]
39    fn pick_transition_idx_is_deterministic_for_same_seed() {
40        let count = 5;
41        assert_eq!(pick_transition_idx(42, count), pick_transition_idx(42, count));
42        assert_eq!(pick_transition_idx(42, count), 42 % 5);
43    }
44
45    #[test]
46    fn pick_transition_idx_distributes_across_seeds() {
47        // Sample 100 seeds, expect at least 3 distinct buckets across 5 transitions.
48        let count = 5;
49        let mut buckets = std::collections::HashSet::new();
50        for seed in 0u64..100 {
51            buckets.insert(pick_transition_idx(seed, count));
52        }
53        assert!(buckets.len() >= 3, "expected >=3 distinct outcomes across 100 seeds, got {}: {buckets:?}", buckets.len());
54    }
55
56    #[test]
57    fn pick_transition_idx_stays_in_bounds() {
58        for count in 1..=10 {
59            for seed in 0u64..50 {
60                let idx = pick_transition_idx(seed, count);
61                assert!(idx < count, "idx {idx} out of range for count {count} seed {seed}");
62            }
63        }
64    }
65}