Skip to main content

rsynko_session/
laws.rs

1//! Law scenarios for attendance, stated once over the capabilities.
2//!
3//! A scenario authors its own runs through a fixture and reads back what each request was told,
4//! so it constrains any interpreter of the sorts rather than the one this workspace ships.
5
6use crate::*;
7use alux_ext::ext;
8use anyhow::{Result, bail};
9use std::fmt::Debug;
10use std::time::Duration;
11
12/// Supplies the runs an attendance scenario cannot author for itself.
13pub trait SessionLawFixture: SessionSorts {
14    /// Authors one request wanting work whose run states the given number of things, one per
15    /// pass, and is over once it has stated them.
16    fn law_wanting(&mut self, says: usize) -> Self::Id;
17
18    /// Authors one request whose work refuses to begin.
19    fn law_unbeginnable(&mut self) -> Self::Id;
20
21    /// States what one request now wants of its run.
22    fn law_wants(&mut self, id: &Self::Id, wanted: Wanted);
23
24    /// Lets the stated time pass.
25    fn law_passes(&mut self, elapsed: Duration);
26
27    /// Observes everything one request was told, in the order it was told.
28    fn law_told(&self, id: &Self::Id) -> Vec<Telling>;
29
30    /// Observes how many times one run was told to end whether or not it had finished.
31    fn law_abandonments(&self, id: &Self::Id) -> usize;
32}
33
34/// Authors the attendance laws.
35#[ext(name = SessionLaws)]
36pub impl<This> This
37where
38    This: UndertakingAlg + RunReadAlg + RunHoldAlg + AttentionAlg + ClockAlg + SessionLawFixture,
39    This::Id: Clone + Eq + Debug,
40{
41    /// Checks that one pass reconciles what is wanted with what is happening.
42    ///
43    /// The laws checked are:
44    ///
45    /// 1. one pass begins every request wanting work, and no other;
46    /// 2. a request whose work has begun is not begun again, however many passes run;
47    /// 3. everything a run said is told before how it ended, and nothing is told after;
48    /// 4. work refusing to begin ends once and is not begun again;
49    /// 5. a run nobody wants is told to end on every pass until it is over, and ends once;
50    /// 6. time a run is held still is not counted as time it was running;
51    /// 7. attending to no runs tells nothing.
52    ///
53    /// # Errors
54    ///
55    /// Returns the first violated law.
56    fn session_laws(&mut self) -> Result<()> {
57        self.check_beginning_laws()?;
58        self.check_saying_laws()?;
59        self.check_unwanted_laws()?;
60        self.check_holding_laws()
61    }
62
63    /// Checks what one pass begins, and what it declines to begin twice.
64    ///
65    /// # Errors
66    ///
67    /// Returns the first violated law.
68    fn check_beginning_laws(&mut self) -> Result<()> {
69        let mut running = Vec::new();
70        self.attend(&mut running);
71        if !running.is_empty() {
72            bail!("attending to nothing began something");
73        }
74
75        let wanting = [self.law_wanting(1), self.law_wanting(1)];
76        let refusing = self.law_unbeginnable();
77        self.attend(&mut running);
78        if running.len() != wanting.len() {
79            bail!(
80                "one pass began {} runs where {} requests wanted work",
81                running.len(),
82                wanting.len()
83            );
84        }
85        for id in &wanting {
86            if begun_count(&self.law_told(id)) != 1 {
87                bail!("{id:?} was not told once that its work began");
88            }
89        }
90        if self.law_told(&refusing) != vec![Telling::Refused] {
91            bail!("work refusing to begin was not stated once as a refusal");
92        }
93
94        self.attend(&mut running);
95        for id in &wanting {
96            if begun_count(&self.law_told(id)) != 1 {
97                bail!("{id:?} was begun again while its run was still happening");
98            }
99        }
100        if self.law_told(&refusing) != vec![Telling::Refused] {
101            bail!("work refusing to begin was begun again");
102        }
103        Ok(())
104    }
105
106    /// Checks that what a run said reaches the request before how it ended.
107    ///
108    /// # Errors
109    ///
110    /// Returns the first violated law.
111    fn check_saying_laws(&mut self) -> Result<()> {
112        let mut running = Vec::new();
113        let said = 3;
114        let id = self.law_wanting(said);
115        self.attend(&mut running);
116        while !running.is_empty() {
117            self.attend(&mut running);
118        }
119
120        let told = self.law_told(&id);
121        let heard = told
122            .iter()
123            .filter(|telling| **telling == Telling::Heard)
124            .count();
125        if heard != said {
126            bail!("a run stating {said} things was heard {heard} times");
127        }
128        let Some(ending) = told.iter().position(|telling| *telling == Telling::Ended) else {
129            bail!("a run that finished never stated how it ended");
130        };
131        if ending + 1 != told.len() {
132            bail!("something was stated after the run had ended");
133        }
134        if told.first() != Some(&Telling::Begun) {
135            bail!("a run stated something before it began");
136        }
137        Ok(())
138    }
139
140    /// Checks how a run nobody wants any more is ended.
141    ///
142    /// # Errors
143    ///
144    /// Returns the first violated law.
145    fn check_unwanted_laws(&mut self) -> Result<()> {
146        let mut running = Vec::new();
147        let id = self.law_wanting(4);
148        self.attend(&mut running);
149        self.law_wants(&id, Wanted::Unwanted);
150
151        let mut passes = 0;
152        while !running.is_empty() {
153            self.attend(&mut running);
154            passes += 1;
155            if passes > 8 {
156                bail!("a run nobody wanted never ended");
157            }
158        }
159        // Told once for every pass it was still happening: a run that has not begun yet cannot
160        // be told to stop, so telling it once would let it escape.
161        if self.law_abandonments(&id) != passes - 1 {
162            bail!(
163                "a run nobody wanted was told to end {} times over {passes} passes",
164                self.law_abandonments(&id)
165            );
166        }
167        let ended = self
168            .law_told(&id)
169            .iter()
170            .filter(|telling| matches!(telling, Telling::Ended | Telling::Refused))
171            .count();
172        if ended != 1 {
173            bail!("a run nobody wanted stated how it ended {ended} times");
174        }
175        Ok(())
176    }
177
178    /// Checks that time a run is held still is not time it was running.
179    ///
180    /// # Errors
181    ///
182    /// Returns the first violated law.
183    fn check_holding_laws(&mut self) -> Result<()> {
184        let mut running = Vec::new();
185        let id = self.law_wanting(5);
186        let tick = Duration::from_secs(1);
187        self.attend(&mut running);
188
189        self.law_passes(tick);
190        self.law_wants(&id, Wanted::Held);
191        self.attend(&mut running);
192        let held_at = last_elapsed(&self.law_told(&id));
193
194        self.law_passes(tick * 10);
195        self.attend(&mut running);
196        if last_elapsed(&self.law_told(&id)) != held_at {
197            bail!("a run held still went on running");
198        }
199
200        self.law_wants(&id, Wanted::Running);
201        self.attend(&mut running);
202        self.law_passes(tick);
203        self.attend(&mut running);
204        if last_elapsed(&self.law_told(&id)) != held_at.map(|elapsed| elapsed + tick) {
205            bail!("a run let go again did not carry on from where it was held");
206        }
207        Ok(())
208    }
209}
210
211/// Denotes one thing a request was told about the run working on its behalf.
212#[derive(Clone, Copy, Debug, PartialEq, Eq)]
213pub enum Telling {
214    /// Denotes being told that a run began.
215    Begun,
216    /// Denotes being told one thing the run said.
217    Heard,
218    /// Denotes being told how long the run has been running.
219    RanFor(Duration),
220    /// Denotes being told that the run ended well.
221    Ended,
222    /// Denotes being told that the run refused.
223    Refused,
224}
225
226/// Counts how many times one request was told its work began.
227fn begun_count(told: &[Telling]) -> usize {
228    told.iter()
229        .filter(|telling| **telling == Telling::Begun)
230        .count()
231}
232
233/// Observes the last running time one request was told, when it was told one.
234fn last_elapsed(told: &[Telling]) -> Option<Duration> {
235    told.iter().rev().find_map(|telling| match telling {
236        Telling::RanFor(elapsed) => Some(*elapsed),
237        Telling::Begun | Telling::Heard | Telling::Ended | Telling::Refused => None,
238    })
239}