lib.rsannotatedlib.rssource264 lines · 9.6 KB · raw

The in-memory reference adapter for whiskers-ports, and the conformance suite every adapter must pass. See README.md.

4#![forbid(unsafe_code)]
6pub mod memory;
7pub mod suite;
8
9pub use memory::MemBackend;
10
11#[cfg(test)]
12mod tests {

The suite, run against the simplest honest implementation: every port in memory. If a law fails here the law is wrong, not the adapter.

16    use super::memory::*;
17    use super::suite::{self, fixture::*};
18    use whiskers_ports::SecretName;
20    #[test]
21    fn the_in_memory_adapter_obeys_every_law_of_every_capability() {
22        let ran = Suite::complete(MemFixtures);
23        assert_eq!(
24            ran.ran(),
25            ["memory", "household", "chat", "chat write faults", "journal", "pictures", "allowance", "judge", "voice", "clock"],
26            "complete means every capability that has a law"
27        );
28    }
29
30    #[test]
31    fn a_harness_runs_exactly_the_laws_of_the_capabilities_it_has() {

A harness with the three hubs and nothing else, as the Worker's is.

33        struct OnlyHubs;
34        impl MemoryFixture for OnlyHubs {
35            type Port = MemMemoryHub;
36            fn fresh(&self) -> MemMemoryHub {
37                MemMemoryHub::default()
38            }
39            fn restart(&self, p: MemMemoryHub) -> MemMemoryHub {
40                p
41            }
42        }
43        impl HouseholdFixture for OnlyHubs {
44            type Port = MemHouseholdHub;
45            fn fresh(&self) -> MemHouseholdHub {
46                MemHouseholdHub::default()
47            }
48            fn restart(&self, p: MemHouseholdHub) -> MemHouseholdHub {
49                p
50            }
51        }
52        impl ChatFixture for OnlyHubs {
53            type Port = MemChatHub;
54            fn fresh(&self) -> MemChatHub {
55                MemChatHub::default()
56            }
57            fn restart(&self, p: MemChatHub) -> MemChatHub {
58                p
59            }
60        }
61        let suite = Suite::new(OnlyHubs).memory().household().chat();
62        assert_eq!(suite.ran(), ["memory", "household", "chat"]);
63        // `suite.journal()` does not compile here (see the `compile_fail` example on `Suite`).
64    }
66    #[test]
67    fn the_secrets_laws_and_the_timing_laws_are_called_on_their_own() {
68        suite::timing::timing_types();
69        suite::timing::speaks_timed(&ScriptedVoice::default());
70    }
71
72    #[test]
73    fn secrets_tell_absent_from_empty() {
74        suite::secrets::secrets(&|entries: &[(SecretName, &str)]| MemSecrets::with(entries));
75    }
76
77    // A suite that cannot fail proves nothing. These adapters are each wrong in exactly one way the
78    // contracts forbid, and the matching law must notice.
79
80    use whiskers_core::{Household, MemorySnapshot};
81    use whiskers_ports::{
82        Allowance, Appended, DeviceCursor, DeviceName, EntryLine, Hub, Journal, LogCursor, Merged, Millis, Page, ReserveError, Settlement,
83        StoreError, ThinkingRoom, ThinkingUsage, Tokens, VoiceReservation, VoiceSpend,
84    };

Replaces its memory with whatever it is given, instead of merging.

87    #[derive(Default)]
88    struct OverwritingMemory(std::sync::Mutex<MemorySnapshot>);
90    impl Hub for OverwritingMemory {
91        type Document = MemorySnapshot;
92        async fn merge(&self, theirs: MemorySnapshot) -> Result<Merged<MemorySnapshot>, StoreError> {
93            *self.0.lock().unwrap() = theirs.clone();
94            Ok(Merged { document: theirs, changed: 1 })
95        }
96        async fn current(&self) -> Result<MemorySnapshot, StoreError> {
97            Ok(self.0.lock().unwrap().clone())
98        }
99    }
100
101    #[test]
102    #[should_panic]
103    fn a_memory_hub_that_overwrites_is_caught() {
104        suite::hubs::memory_hub(&OverwritingMemory::default, &|h| h);
105    }

Merges well, but takes no notice of her putting a fact away: every incoming fact is shown.

108    #[derive(Default)]
109    struct BlindToHiding(super::memory::MemMemoryHub);
111    impl Hub for BlindToHiding {
112        type Document = MemorySnapshot;
113        async fn merge(&self, mut theirs: MemorySnapshot) -> Result<Merged<MemorySnapshot>, StoreError> {
114            for f in &mut theirs.facts {
115                f.visibility = whiskers_core::Visibility::default();
116            }
117            self.0.merge(theirs).await
118        }
119        async fn current(&self) -> Result<MemorySnapshot, StoreError> {
120            self.0.current().await
121        }
122    }
123
124    #[test]
125    #[should_panic]
126    fn a_memory_hub_that_loses_a_hide_is_caught() {
127        suite::hubs::memory_a_hide_is_kept_and_the_later_write_wins(&BlindToHiding::default);
128    }

Merges well, but a copy's picture replaces the hub's whatever it is (last one in wins).

131    #[derive(Default)]
132    struct LastPictureWins(super::memory::MemMemoryHub);
134    impl Hub for LastPictureWins {
135        type Document = MemorySnapshot;
136        async fn merge(&self, theirs: MemorySnapshot) -> Result<Merged<MemorySnapshot>, StoreError> {
137            let mut merged = self.0.merge(theirs.clone()).await?;
138            for f in &mut merged.document.facts {
139                if let Some(t) = theirs.facts.iter().find(|t| t.gid == f.gid && t.icon.is_some()) {
140                    f.icon = t.icon.clone();
141                }
142            }
143            Ok(merged)
144        }
145        async fn current(&self) -> Result<MemorySnapshot, StoreError> {
146            self.0.current().await
147        }
148    }
149
150    #[test]
151    #[should_panic]
152    fn a_memory_hub_where_the_last_picture_wins_is_caught() {
153        suite::hubs::memory_a_picture_chosen_anywhere_serves_everywhere(&LastPictureWins::default);
154    }

Treats a hide as a forget: tombstones the identity and drops the fact.

157    #[derive(Default)]
158    struct HideMeansForget(super::memory::MemMemoryHub);
160    impl Hub for HideMeansForget {
161        type Document = MemorySnapshot;
162        async fn merge(&self, mut theirs: MemorySnapshot) -> Result<Merged<MemorySnapshot>, StoreError> {
163            let (away, shown): (Vec<_>, Vec<_>) = theirs.facts.drain(..).partition(|f| f.visibility.is_hidden());
164            theirs.facts = shown;
165            theirs.forgotten.extend(away.into_iter().map(|f| f.gid));
166            self.0.merge(theirs).await
167        }
168        async fn current(&self) -> Result<MemorySnapshot, StoreError> {
169            self.0.current().await
170        }
171    }
172
173    #[test]
174    #[should_panic]
175    fn a_memory_hub_that_forgets_what_she_put_away_is_caught() {
176        suite::hubs::memory_a_hidden_fact_is_not_a_forgotten_one(&HideMeansForget::default);
177    }

Reports every merge as a change.

180    #[derive(Default)]
181    struct AlwaysChanged(MemHouseholdHub);
183    impl Hub for AlwaysChanged {
184        type Document = Household;
185        async fn merge(&self, theirs: Household) -> Result<Merged<Household>, StoreError> {
186            let mut m = self.0.merge(theirs).await?;
187            m.changed = 1;
188            Ok(m)
189        }
190        async fn current(&self) -> Result<Household, StoreError> {
191            self.0.current().await
192        }
193    }
194
195    #[test]
196    #[should_panic]
197    fn a_household_hub_that_always_reports_a_change_is_caught() {
198        suite::hubs::household_hub(&AlwaysChanged::default, &|h| h);
199    }

Takes lines wherever the device says it is, and so duplicates.

202    #[derive(Default)]
203    struct TrustingJournal(MemJournal);
205    impl Journal for TrustingJournal {
206        async fn held(&self, d: &DeviceName) -> Result<DeviceCursor, StoreError> {
207            self.0.held(d).await
208        }
209        async fn append(&self, d: &DeviceName, _at: DeviceCursor, lines: &[EntryLine]) -> Result<Appended, StoreError> {
210            let held = self.0.held(d).await?;
211            self.0.append(d, held, lines).await
212        }
213        async fn pull(&self, since: LogCursor) -> Result<Page, StoreError> {
214            self.0.pull(since).await
215        }
216    }
217
218    #[test]
219    #[should_panic]
220    fn a_journal_that_ignores_where_the_device_left_off_is_caught() {
221        suite::journal::journal(&TrustingJournal::default, &|j| j);
222    }

Checks the cap against settled spending only, so two outstanding lines both pass.

225    #[derive(Default)]
226    struct SettledOnly {
227        inner: MemAllowance,
228        settled: std::sync::Mutex<u32>,
229    }
231    impl Allowance for SettledOnly {
232        async fn thinking_room(&self, now: Millis, l: &whiskers_core::TokenLimit) -> Result<ThinkingRoom, StoreError> {
233            self.inner.thinking_room(now, l).await
234        }
235        async fn charge_thinking(&self, now: Millis, c: Tokens) -> Result<(), StoreError> {
236            self.inner.charge_thinking(now, c).await
237        }
238        async fn thinking_usage(&self, now: Millis, l: &whiskers_core::TokenLimit) -> Result<ThinkingUsage, StoreError> {
239            self.inner.thinking_usage(now, l).await
240        }
241        async fn reserve_voice(&self, now: Millis, chars: u32, cap: u32) -> Result<VoiceReservation, ReserveError> {
242            if *self.settled.lock().unwrap() + chars > cap {
243                return self.inner.reserve_voice(now, cap + 1, cap).await; // refuses, with the real day
244            }
245            let r = self.inner.reserve_voice(now, chars, u32::MAX).await?;
246            Ok(r)
247        }
248        async fn settle_voice(&self, r: VoiceReservation, how: Settlement) -> Result<(), StoreError> {
249            if matches!(how, Settlement::Spoken) {
250                *self.settled.lock().unwrap() += r.chars();
251            }
252            self.inner.settle_voice(r, how).await
253        }
254        async fn voice_spend(&self, now: Millis) -> Result<VoiceSpend, StoreError> {
255            self.inner.voice_spend(now).await
256        }
257    }
258
259    #[test]
260    #[should_panic]
261    fn an_allowance_that_forgets_outstanding_reservations_is_caught() {
262        suite::allowance::allowance(&SettledOnly::default, &|a| a);
263    }
264}