1//! Laws selected by capability. 2//! 3//! An adapter's harness says what it can make by implementing the fixture trait of each capability it has 4//! (`MemoryFixture`, `JournalFixture`, ...); [`Suite`] then offers exactly the law sets of those capabilities. 5//! A law set for a capability the harness has no fixture for is not skipped, it cannot be selected: the method 6//! that would select it does not apply to the harness, and the compiler says which fixture is missing. 7//! 8//! The fixture traits sit beside the `Has…` capability traits of `whiskers-ports` and mirror them: a capability 9//! the service can be given (`HasJournal`) has a fixture the suite can be given (`JournalFixture`). A 10//! harness for a whole adapter implements [`Fixtures`], which [`Suite::complete`] runs in full. 11//! 12//! Capabilities with no fixture here have no law yet: the embedder and the thinker are held to nothing a 13//! conformance run can show without calling out (what a real model server says back is the adapter's own 14//! test), and `Secrets` is not served by a route; its laws are `suite::secrets`. 15 16use whiskers_ports::{Allowance, ChatHub, Clock, HouseholdHub, Journal, Judge, MemoryHub, PictureShelf, Voice}; 17 18use super::hubs::WriteSwitch; 19use super::{allowance, hubs, journal, outbound, pictures}; 20 21macro_rules! stateful_fixture { 22 ($(#[$doc:meta])* $name:ident, $port:ident) => { 23 $(#[$doc])* 24 pub trait $name { 25 type Port: $port; 26 /// A new, empty port of this capability. 27 fn fresh(&self) -> Self::Port; 28 /// The same port after its process restarted: it must find what it kept. 29 fn restart(&self, port: Self::Port) -> Self::Port; 30 } 31 }; 32} 33 34stateful_fixture!( 35 /// How to make, and restart, the memory hub (capability `HasMemory`). 36 MemoryFixture, MemoryHub 37); 38stateful_fixture!( 39 /// How to make, and restart, the household hub (capability `HasHousehold`). 40 HouseholdFixture, HouseholdHub 41); 42stateful_fixture!( 43 /// How to make, and restart, the chat hub (capability `HasChat`). 44 ChatFixture, ChatHub 45); 46stateful_fixture!( 47 /// How to make, and restart, the journal (capability `HasJournal`). 48 JournalFixture, Journal 49); 50stateful_fixture!( 51 /// How to make, and restart, the picture shelf (capability `HasPictures`). 52 PicturesFixture, PictureShelf 53); 54stateful_fixture!( 55 /// How to make, and restart, the allowance (capability `HasAllowance`). 56 AllowanceFixture, Allowance 57); 58 59/// How to make a chat hub whose storage can be told to refuse writes, as a full disk would. Not every harness 60/// can (a black box over HTTP cannot), so the law that needs it is selected apart from the chat hub's others. 61pub trait ChatFaultFixture { 62 type Port: ChatHub; 63 /// A new chat hub and the switch that makes its storage refuse writes (`true`) or take them again. 64 fn fresh_refusing(&self) -> (Self::Port, WriteSwitch); 65 /// The same hub after a restart, its storage working again. 66 fn restart_refusing(&self, port: Self::Port) -> Self::Port; 67} 68 69/// A judge with nothing to call with (capability `HasJudge`). 70pub trait JudgeFixture { 71 type Port: Judge; 72 fn without_credentials(&self) -> Self::Port; 73} 74 75/// A voice with nothing to call with (capability `HasVoice`). The law that has it speak, which a paid voice 76/// runs only when its operator agrees to the spend, is `suite::timing::speaks_timed`, called on purpose. 77pub trait VoiceFixture { 78 type Port: Voice; 79 fn without_credentials(&self) -> Self::Port; 80} 81 82/// A clock (capability `HasClock`). 83pub trait ClockFixture { 84 type Port: Clock; 85 fn clock(&self) -> Self::Port; 86} 87 88/// A harness for a whole adapter: a fixture for every capability there is a law for. 89pub trait Fixtures: 90 MemoryFixture + HouseholdFixture + ChatFixture + ChatFaultFixture + JournalFixture + PicturesFixture + AllowanceFixture + JudgeFixture + VoiceFixture + ClockFixture 91{ 92} 93 94impl<F> Fixtures for F where 95 F: MemoryFixture 96 + HouseholdFixture 97 + ChatFixture 98 + ChatFaultFixture 99 + JournalFixture 100 + PicturesFixture 101 + AllowanceFixture 102 + JudgeFixture 103 + VoiceFixture 104 + ClockFixture 105{ 106} 107 108/// The capabilities whose laws a [`Suite`] has run, by name, in the order run. 109pub type Ran = Vec<&'static str>; 110 111/// A harness, and the laws chosen for it so far. Every method runs the laws of one capability, and applies 112/// only to a harness with that capability's fixture. A harness with the memory hub and nothing else can run 113/// the memory hub's laws: 114/// 115/// ``` 116/// use whiskers_conformance::memory::MemMemoryHub; 117/// use whiskers_conformance::suite::fixture::{MemoryFixture, Suite}; 118/// 119/// struct OnlyMemory; 120/// impl MemoryFixture for OnlyMemory { 121/// type Port = MemMemoryHub; 122/// fn fresh(&self) -> MemMemoryHub { MemMemoryHub::default() } 123/// fn restart(&self, port: MemMemoryHub) -> MemMemoryHub { port } 124/// } 125/// 126/// let suite = Suite::new(OnlyMemory).memory(); 127/// assert_eq!(suite.ran(), ["memory"]); 128/// ``` 129/// 130/// It cannot select the journal's laws. They are not skipped, there is no way to ask for them: 131/// 132/// ```compile_fail 133/// use whiskers_conformance::memory::MemMemoryHub; 134/// use whiskers_conformance::suite::fixture::{MemoryFixture, Suite}; 135/// 136/// struct OnlyMemory; 137/// impl MemoryFixture for OnlyMemory { 138/// type Port = MemMemoryHub; 139/// fn fresh(&self) -> MemMemoryHub { MemMemoryHub::default() } 140/// fn restart(&self, port: MemMemoryHub) -> MemMemoryHub { port } 141/// } 142/// 143/// // error[E0277]: the trait bound `OnlyMemory: JournalFixture` is not satisfied 144/// let _ = Suite::new(OnlyMemory).memory().journal(); 145/// ``` 146pub struct Suite<F> { 147 fixture: F, 148 ran: Ran, 149} 150 151impl<F> Suite<F> { 152 /// A suite that has run nothing. 153 pub fn new(fixture: F) -> Self { 154 Self { fixture, ran: Vec::new() } 155 } 156 157 /// Which capabilities' laws have run. 158 pub fn ran(&self) -> &[&'static str] { 159 &self.ran 160 } 161 162 /// The memory hub's laws. 163 pub fn memory(mut self) -> Self 164 where 165 F: MemoryFixture, 166 { 167 hubs::memory_hub(&|| MemoryFixture::fresh(&self.fixture), &|p| MemoryFixture::restart(&self.fixture, p)); 168 self.ran.push("memory"); 169 self 170 } 171 172 /// The household hub's laws. 173 pub fn household(mut self) -> Self 174 where 175 F: HouseholdFixture, 176 { 177 hubs::household_hub(&|| HouseholdFixture::fresh(&self.fixture), &|p| HouseholdFixture::restart(&self.fixture, p)); 178 self.ran.push("household"); 179 self 180 } 181 182 /// The chat hub's laws, all but the one that needs its storage to fail ([`Suite::chat_write_faults`]). 183 pub fn chat(mut self) -> Self 184 where 185 F: ChatFixture, 186 { 187 hubs::chat_hub_whose_storage_works(&|| ChatFixture::fresh(&self.fixture), &|p| ChatFixture::restart(&self.fixture, p)); 188 self.ran.push("chat"); 189 self 190 } 191 192 /// The chat-hub law that a copy that cannot be kept is an error and changes nothing. 193 pub fn chat_write_faults(mut self) -> Self 194 where 195 F: ChatFaultFixture, 196 { 197 hubs::chat_a_copy_that_cannot_be_kept_is_an_error_and_changes_nothing( 198 &|| ChatFaultFixture::fresh_refusing(&self.fixture), 199 &|p| ChatFaultFixture::restart_refusing(&self.fixture, p), 200 ); 201 self.ran.push("chat write faults"); 202 self 203 } 204 205 /// The journal's laws. 206 pub fn journal(mut self) -> Self 207 where 208 F: JournalFixture, 209 { 210 journal::journal(&|| JournalFixture::fresh(&self.fixture), &|p| JournalFixture::restart(&self.fixture, p)); 211 self.ran.push("journal"); 212 self 213 } 214 215 /// The picture shelf's laws. 216 pub fn pictures(mut self) -> Self 217 where 218 F: PicturesFixture, 219 { 220 pictures::pictures(&|| PicturesFixture::fresh(&self.fixture), &|p| PicturesFixture::restart(&self.fixture, p)); 221 self.ran.push("pictures"); 222 self 223 } 224 225 /// The allowance's laws. 226 pub fn allowance(mut self) -> Self 227 where 228 F: AllowanceFixture, 229 { 230 allowance::allowance(&|| AllowanceFixture::fresh(&self.fixture), &|p| AllowanceFixture::restart(&self.fixture, p)); 231 self.ran.push("allowance"); 232 self 233 } 234 235 /// What a judge with no credentials does. 236 pub fn judge(mut self) -> Self 237 where 238 F: JudgeFixture, 239 { 240 outbound::judge_without_credentials(&JudgeFixture::without_credentials(&self.fixture)); 241 self.ran.push("judge"); 242 self 243 } 244 245 /// What a voice with no credentials does. 246 pub fn voice(mut self) -> Self 247 where 248 F: VoiceFixture, 249 { 250 outbound::voice_without_credentials(&VoiceFixture::without_credentials(&self.fixture)); 251 self.ran.push("voice"); 252 self 253 } 254 255 /// The clock's laws. 256 pub fn clock(mut self) -> Self 257 where 258 F: ClockFixture, 259 { 260 outbound::clock(&ClockFixture::clock(&self.fixture)); 261 self.ran.push("clock"); 262 self 263 } 264 265 /// Every law there is for every capability, for a harness of a whole adapter. 266 pub fn complete(fixture: F) -> Self 267 where 268 F: Fixtures, 269 { 270 Self::new(fixture).memory().household().chat().chat_write_faults().journal().pictures().allowance().judge().voice().clock() 271 } 272}