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!("one pass began {} runs where {} requests wanted work", running.len(), wanting.len());
80        }
81        for id in &wanting {
82            if begun_count(&self.law_told(id)) != 1 {
83                bail!("{id:?} was not told once that its work began");
84            }
85        }
86        if self.law_told(&refusing) != vec![Telling::Refused] {
87            bail!("work refusing to begin was not stated once as a refusal");
88        }
89
90        self.attend(&mut running);
91        for id in &wanting {
92            if begun_count(&self.law_told(id)) != 1 {
93                bail!("{id:?} was begun again while its run was still happening");
94            }
95        }
96        if self.law_told(&refusing) != vec![Telling::Refused] {
97            bail!("work refusing to begin was begun again");
98        }
99        Ok(())
100    }
101
102    /// Checks that what a run said reaches the request before how it ended.
103    ///
104    /// # Errors
105    ///
106    /// Returns the first violated law.
107    fn check_saying_laws(&mut self) -> Result<()> {
108        let mut running = Vec::new();
109        let said = 3;
110        let id = self.law_wanting(said);
111        self.attend(&mut running);
112        while !running.is_empty() {
113            self.attend(&mut running);
114        }
115
116        let told = self.law_told(&id);
117        let heard = told.iter().filter(|telling| **telling == Telling::Heard).count();
118        if heard != said {
119            bail!("a run stating {said} things was heard {heard} times");
120        }
121        let Some(ending) = told.iter().position(|telling| *telling == Telling::Ended) else {
122            bail!("a run that finished never stated how it ended");
123        };
124        if ending + 1 != told.len() {
125            bail!("something was stated after the run had ended");
126        }
127        if told.first() != Some(&Telling::Begun) {
128            bail!("a run stated something before it began");
129        }
130        Ok(())
131    }
132
133    /// Checks how a run nobody wants any more is ended.
134    ///
135    /// # Errors
136    ///
137    /// Returns the first violated law.
138    fn check_unwanted_laws(&mut self) -> Result<()> {
139        let mut running = Vec::new();
140        let id = self.law_wanting(4);
141        self.attend(&mut running);
142        self.law_wants(&id, Wanted::Unwanted);
143
144        let mut passes = 0;
145        while !running.is_empty() {
146            self.attend(&mut running);
147            passes += 1;
148            if passes > 8 {
149                bail!("a run nobody wanted never ended");
150            }
151        }
152        // Told once for every pass it was still happening: a run that has not begun yet cannot
153        // be told to stop, so telling it once would let it escape.
154        if self.law_abandonments(&id) != passes - 1 {
155            bail!("a run nobody wanted was told to end {} times over {passes} passes", self.law_abandonments(&id));
156        }
157        let ended =
158            self.law_told(&id).iter().filter(|telling| matches!(telling, Telling::Ended | Telling::Refused)).count();
159        if ended != 1 {
160            bail!("a run nobody wanted stated how it ended {ended} times");
161        }
162        Ok(())
163    }
164
165    /// Checks that time a run is held still is not time it was running.
166    ///
167    /// # Errors
168    ///
169    /// Returns the first violated law.
170    fn check_holding_laws(&mut self) -> Result<()> {
171        let mut running = Vec::new();
172        let id = self.law_wanting(5);
173        let tick = Duration::from_secs(1);
174        self.attend(&mut running);
175
176        self.law_passes(tick);
177        self.law_wants(&id, Wanted::Held);
178        self.attend(&mut running);
179        let held_at = last_elapsed(&self.law_told(&id));
180
181        self.law_passes(tick * 10);
182        self.attend(&mut running);
183        if last_elapsed(&self.law_told(&id)) != held_at {
184            bail!("a run held still went on running");
185        }
186
187        self.law_wants(&id, Wanted::Running);
188        self.attend(&mut running);
189        self.law_passes(tick);
190        self.attend(&mut running);
191        if last_elapsed(&self.law_told(&id)) != held_at.map(|elapsed| elapsed + tick) {
192            bail!("a run let go again did not carry on from where it was held");
193        }
194        Ok(())
195    }
196}
197
198/// Denotes one thing a request was told about the run working on its behalf.
199#[derive(Clone, Copy, Debug, PartialEq, Eq)]
200pub enum Telling {
201    /// Denotes being told that a run began.
202    Begun,
203    /// Denotes being told one thing the run said.
204    Heard,
205    /// Denotes being told how long the run has been running.
206    RanFor(Duration),
207    /// Denotes being told that the run ended well.
208    Ended,
209    /// Denotes being told that the run refused.
210    Refused,
211}
212
213/// Counts how many times one request was told its work began.
214fn begun_count(told: &[Telling]) -> usize {
215    told.iter().filter(|telling| **telling == Telling::Begun).count()
216}
217
218/// Observes the last running time one request was told, when it was told one.
219fn last_elapsed(told: &[Telling]) -> Option<Duration> {
220    told.iter().rev().find_map(|telling| match telling {
221        Telling::RanFor(elapsed) => Some(*elapsed),
222        Telling::Begun | Telling::Heard | Telling::Ended | Telling::Refused => None,
223    })
224}