The SQLite adapters held to the same laws as the in-memory one (whiskers-conformance), plus what only real
threads and files show.
4mod fixtures;
6use std::sync::Arc; 7 8use fixtures::{SqliteFixtures, scratch}; 9use whiskers_conformance::suite::fixture::*; 10use whiskers_conformance::suite::{self}; 11use whiskers_core::TokenLimit; 12use whiskers_ports::{Allowance, Appended, DeviceCursor, DeviceName, EntryLine, Hub, Journal, LogCursor, Millis, PictureShelf, Put, SafePictureId, Settlement, run_ready}; 13use whiskers_store::{SqliteAllowance, SqliteJournal, SqliteMemoryHub, SqlitePictureShelf, Store};
The store's adapters are held to every law there is: if a capability gains a law and a fixture trait, this stops compiling until the fixtures have it.
17const _EVERY_STORAGE_LAW_IS_RUN: fn(SqliteFixtures) -> Suite<SqliteFixtures> = |f| Suite::new(f).memory().household().chat().chat_write_faults().journal().pictures().allowance().speech_cache();
19#[test] 20fn the_sqlite_memory_hub_obeys_its_contract() { 21 Suite::new(SqliteFixtures).memory(); 22} 23 24#[test] 25fn the_sqlite_household_hub_obeys_its_contract() { 26 Suite::new(SqliteFixtures).household(); 27} 28 29#[test] 30fn the_sqlite_chat_hub_obeys_its_contract() { 31 Suite::new(SqliteFixtures).chat().chat_write_faults(); 32} 33 34#[test] 35fn the_sqlite_journal_obeys_its_contract() { 36 Suite::new(SqliteFixtures).journal(); 37} 38 39#[test] 40fn the_sqlite_picture_shelf_obeys_its_contract() { 41 Suite::new(SqliteFixtures).pictures(); 42} 43 44#[test] 45fn the_sqlite_allowance_obeys_its_contract() { 46 Suite::new(SqliteFixtures).allowance(); 47}
55#[test] 56fn eight_lines_asked_at_once_cannot_pass_a_cap_with_room_for_one() { 57 let a = Arc::new(SqliteAllowance::open(Store::open(&scratch("race-voice")).unwrap())); 58 let threads: Vec<_> = (0..8) 59 .map(|_| { 60 let a = a.clone(); 61 std::thread::spawn(move || run_ready(a.reserve_voice(at(1000), 6, 10)).map(|r| run_ready(a.settle_voice(r, Settlement::Spoken)).unwrap())) 62 }) 63 .collect(); 64 let results: Vec<_> = threads.into_iter().map(|t| t.join().unwrap()).collect(); 65 assert_eq!(results.iter().filter(|r| r.is_ok()).count(), 1, "exactly one line fits"); 66 assert_eq!(run_ready(a.voice_spend(at(1001))).unwrap().spent_today, 6); 67} 68 69#[test] 70fn many_threads_pushing_at_one_cursor_take_exactly_one_push() { 71 let j = Arc::new(SqliteJournal::new(Store::open(&scratch("race-journal")).unwrap())); 72 let device = DeviceName::new("phone1").unwrap(); 73 let accepted: usize = (0..8) 74 .map(|_| { 75 let (j, device) = (j.clone(), device.clone()); 76 std::thread::spawn(move || { 77 let lines = [EntryLine::parse(r#"{"n":1}"#).unwrap(), EntryLine::parse(r#"{"n":2}"#).unwrap()]; 78 matches!(run_ready(j.append(&device, DeviceCursor::new(0), &lines)).unwrap(), Appended::Accepted { .. }) 79 }) 80 }) 81 .collect::<Vec<_>>() 82 .into_iter() 83 .map(|t| usize::from(t.join().unwrap())) 84 .sum(); 85 assert_eq!(accepted, 1); 86 assert_eq!(run_ready(j.pull(LogCursor::START)).unwrap().total.get(), 2); 87} 88 89#[test] 90fn merges_that_arrive_together_all_land_and_no_fact_is_numbered_twice() { 91 let hub = Arc::new(SqliteMemoryHub::open(Store::open(&scratch("race-memory")).unwrap()).unwrap()); 92 let threads: Vec<_> = (0..8) 93 .map(|i| { 94 let hub = hub.clone(); 95 std::thread::spawn(move || { 96 let doc = suite::build::snapshot(vec![suite::build::fact(&format!("g{i}"), &format!("fact number {i}"))], &[]); 97 run_ready(hub.merge(doc)).unwrap(); 98 }) 99 }) 100 .collect(); 101 for t in threads { 102 t.join().unwrap(); 103 } 104 let doc = run_ready(hub.current()).unwrap(); 105 assert_eq!(doc.facts.len(), 8, "no merge lost another's work"); 106 let ids: std::collections::BTreeSet<u64> = doc.facts.iter().map(|f| f.id).collect(); 107 assert_eq!(ids.len(), 8, "and no id was handed out twice"); 108} 109 110#[test] 111fn eight_puts_of_one_picture_store_it_once_and_it_is_never_seen_half_written() { 112 let shelf = Arc::new(SqlitePictureShelf::new(Store::open(&scratch("race-pictures")).unwrap())); 113 let id = SafePictureId::parse("a.jpg").unwrap(); 114 let body = vec![7u8; 1 << 20]; 115 let threads: Vec<_> = (0..8) 116 .map(|_| { 117 let (shelf, id, body) = (shelf.clone(), id.clone(), body.clone()); 118 std::thread::spawn(move || { 119 let put = run_ready(shelf.put(&id, &body)).unwrap(); 120 if let Some(bytes) = run_ready(shelf.get(&id)).unwrap() { 121 assert_eq!(bytes.len(), body.len()); 122 } 123 put 124 }) 125 }) 126 .collect(); 127 let puts: Vec<Put> = threads.into_iter().map(|t| t.join().unwrap()).collect(); 128 assert_eq!(puts.iter().filter(|p| **p == Put::Stored).count(), 1); 129 assert_eq!(puts.iter().filter(|p| **p == Put::AlreadyKept).count(), 7); 130} 131 132#[test] 133fn the_ledger_and_the_spend_are_found_again_by_the_next_process() { 134 let path = scratch("restart-spend"); 135 let a = SqliteAllowance::open(Store::open(&path).unwrap()); 136 run_ready(a.charge_thinking(at(10), whiskers_ports::Tokens::new(700))).unwrap(); 137 let r = run_ready(a.reserve_voice(at(10), 4, 10)).unwrap(); 138 run_ready(a.settle_voice(r, Settlement::Spoken)).unwrap(); 139 drop(a); 140 let again = SqliteAllowance::open(Store::open(&path).unwrap()); 141 let limit = TokenLimit::new(Some(1000), 5).unwrap(); 142 assert_eq!(run_ready(again.thinking_usage(at(20), &limit)).unwrap().used.get(), 700); 143 assert_eq!(run_ready(again.voice_spend(at(20))).unwrap().spent_today, 4); 144} 145 146#[test] 147fn the_sqlite_speech_cache_obeys_its_contract() { 148 Suite::new(SqliteFixtures).speech_cache(); 149}