1//! The in-memory reference adapter for `whiskers-ports`, and the conformance suite every adapter must 2//! pass. See README.md. 3 4#![forbid(unsafe_code)] 5 6pub mod memory; 7pub mod suite; 8 9pub use memory::MemBackend; 10 11#[cfg(test)] 12mod tests { 13 //! The suite, run against the simplest honest implementation: every port in memory. If a law 14 //! fails here the law is wrong, not the adapter. 15 16 use super::memory::*; 17 use super::suite::{self, fixture::*}; 18 use whiskers_ports::SecretName; 19 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() { 32 /// 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 } 65 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 }; 85 86 /// Replaces its memory with whatever it is given, instead of merging. 87 #[derive(Default)] 88 struct OverwritingMemory(std::sync::Mutex<MemorySnapshot>); 89 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 } 106 107 /// 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); 110 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 } 129 130 /// 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); 133 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 } 155 156 /// Treats a hide as a forget: tombstones the identity and drops the fact. 157 #[derive(Default)] 158 struct HideMeansForget(super::memory::MemMemoryHub); 159 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 } 178 179 /// Reports every merge as a change. 180 #[derive(Default)] 181 struct AlwaysChanged(MemHouseholdHub); 182 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 } 200 201 /// Takes lines wherever the device says it is, and so duplicates. 202 #[derive(Default)] 203 struct TrustingJournal(MemJournal); 204 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 } 223 224 /// 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 } 230 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}