Skip to main content

pure_stage/simulation/
blocked.rs

1// Copyright 2025 PRAGMA
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt;
16
17use crate::{Effect, Instant, Name};
18
19/// Classification of why [`SimulationRunning::run_until_blocked`](crate::simulation::SimulationRunning::run_until_blocked) has stopped.
20#[derive(Debug, PartialEq)]
21pub enum Blocked {
22    /// All stages are suspended on [`Effect::Receive`].
23    Idle,
24    /// The simulation is waiting for a wakeup with no external effects pending.
25    Sleeping { next_wakeup: Instant },
26    /// All stages are suspended on either [`Effect::Receive`] or [`Effect::Send`].
27    Deadlock(Vec<SendBlock>),
28    /// The given breakpoint was hit.
29    Breakpoint(Name, Effect),
30    /// The given stages are suspended on effects other than [`Effect::Receive`]
31    /// while none are suspended on [`Effect::Send`]. The given number of
32    /// external effects are currently unresolved.
33    Busy { external_effects: usize, stages: Vec<Name> },
34    /// The given stage has terminated.
35    Terminated(Name),
36}
37
38#[derive(Debug, PartialEq)]
39pub struct SendBlock {
40    pub from: Name,
41    pub to: Name,
42    pub is_call: bool,
43}
44
45impl Blocked {
46    /// Assert that the blocking reason is `Idle`.
47    #[track_caller]
48    pub fn assert_idle(&self) {
49        match self {
50            Blocked::Idle => {}
51            _ => panic!("expected idle, got {:?}", self),
52        }
53    }
54
55    /// Assert that the blocking reason is `Sleeping`.
56    #[track_caller]
57    pub fn assert_sleeping(&self) -> Instant {
58        match self {
59            Blocked::Sleeping { next_wakeup } => *next_wakeup,
60            _ => panic!("expected sleeping, got {:?}", self),
61        }
62    }
63
64    /// Assert that the blocking reason is `Sleeping` until the given instant.
65    #[track_caller]
66    pub fn assert_sleeping_until(&self, until: Instant) {
67        match self {
68            Blocked::Sleeping { next_wakeup } => {
69                assert_eq!(*next_wakeup, until);
70            }
71            _ => panic!("expected sleeping until {:?}, got {:?}", until, self),
72        }
73    }
74
75    /// Assert that the blocking reason is `Deadlock` by at least the given stages.
76    #[track_caller]
77    pub fn assert_deadlock(&self, names: impl IntoIterator<Item = impl AsRef<str> + fmt::Debug>) {
78        let names = names.into_iter().collect::<Vec<_>>();
79        match self {
80            Blocked::Deadlock(deadlock)
81                if deadlock.iter().all(|send| names.iter().any(|n| name_match(&send.from, n.as_ref()))) => {}
82            _ => panic!("expected deadlock by {:?}, got {:?}", names, self),
83        }
84    }
85
86    /// Assert that the blocking reason is `Busy` by at least the given stages.
87    #[track_caller]
88    pub fn assert_busy(&self, names: impl IntoIterator<Item = impl AsRef<str> + fmt::Debug>) -> &Self {
89        let names = names.into_iter().collect::<Vec<_>>();
90        match self {
91            Blocked::Busy { stages, .. } if names.iter().all(|n| stages.iter().any(|s| name_match(s, n.as_ref()))) => {}
92            _ => panic!("expected busy by {:?}, got {:?}", names, self),
93        }
94        self
95    }
96
97    #[track_caller]
98    pub fn assert_external_effects(&self, effects: usize) {
99        let Self::Busy { external_effects, .. } = self else {
100            panic!("expected busy state but got {:?}", self);
101        };
102        assert_eq!(*external_effects, effects);
103    }
104
105    /// Assert that the blocking reason is `Breakpoint` by the given name.
106    #[track_caller]
107    pub fn assert_breakpoint(self, name: impl AsRef<str>) -> Effect {
108        match self {
109            Blocked::Breakpoint(n, eff) if name_match(&n, name.as_ref()) => eff,
110            _ => panic!("expected breakpoint `{}`, got {:?}", name.as_ref(), self),
111        }
112    }
113
114    /// Assert that the blocking reason is `Terminated` by the given name.
115    #[track_caller]
116    pub fn assert_terminated(self, name: impl AsRef<str>) {
117        match self {
118            Blocked::Terminated(n) if name_match(&n, name.as_ref()) => {}
119            _ => panic!("expected terminated `{}`, got {:?}", name.as_ref(), self),
120        }
121    }
122}
123
124fn name_match(name: &Name, given: &str) -> bool {
125    let name = name.as_str();
126    if name == given {
127        return true;
128    }
129    if name.starts_with(given) && name.get(given.len()..).expect("`given` was valid UTF-8").starts_with('-') {
130        return name[given.len() + 1..].parse::<u64>().is_ok();
131    }
132    false
133}