logicaffeine_system/
testworld.rs1use std::cell::RefCell;
15use std::collections::HashMap;
16
17pub const SEALED_EPOCH_MS: u64 = 1_577_836_800_000;
20
21#[derive(Debug, Clone, Copy, Default)]
23pub struct Capabilities {
24 pub filesystem: bool,
25 pub network: bool,
26 pub environment: bool,
27 pub clock: bool,
28}
29
30#[derive(Debug)]
32pub struct TestWorld {
33 pub caps: Capabilities,
34 pub clock_ms: u64,
36 pub files: HashMap<String, String>,
38 pub env: HashMap<String, String>,
40 pub rng: u64,
42}
43
44impl TestWorld {
45 pub fn new(caps: Capabilities, seed: u64) -> Self {
47 TestWorld {
48 caps,
49 clock_ms: SEALED_EPOCH_MS,
50 files: HashMap::new(),
51 env: HashMap::new(),
52 rng: seed,
53 }
54 }
55
56 fn next_u64(&mut self) -> u64 {
57 self.rng = self.rng.wrapping_add(0x9E37_79B9_7F4A_7C15);
59 let mut z = self.rng;
60 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
61 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
62 z ^ (z >> 31)
63 }
64}
65
66thread_local! {
67 static WORLD: RefCell<Option<TestWorld>> = const { RefCell::new(None) };
68}
69
70pub fn install(world: TestWorld) {
72 WORLD.with(|w| *w.borrow_mut() = Some(world));
73}
74
75pub fn uninstall() {
77 WORLD.with(|w| *w.borrow_mut() = None);
78}
79
80pub fn take() -> Option<TestWorld> {
84 WORLD.with(|w| w.borrow_mut().take())
85}
86
87pub fn restore(world: Option<TestWorld>) {
89 WORLD.with(|w| *w.borrow_mut() = world);
90}
91
92pub fn with_world<R>(f: impl FnOnce(&mut TestWorld) -> R) -> Option<R> {
94 WORLD.with(|w| w.borrow_mut().as_mut().map(f))
95}
96
97pub fn seed_file(path: String, content: String) {
99 with_world(|w| {
100 w.files.insert(path, content);
101 });
102}
103
104pub fn advance_clock(ms: u64) -> bool {
107 with_world(|w| w.clock_ms = w.clock_ms.saturating_add(ms)).is_some()
108}
109
110pub fn intercept_read(path: &str) -> Option<Result<String, String>> {
113 with_world(|w| {
114 if w.caps.filesystem {
115 return None;
116 }
117 Some(match w.files.get(path) {
118 Some(content) => Ok(content.clone()),
119 None => Err(format!(
120 "'{path}' is not in the test's sealed filesystem — seed it with \
121 `Given a file \"{path}\" containing \"…\".`, or grant \
122 `This test may use the real filesystem.`"
123 )),
124 })
125 })
126 .flatten()
127}
128
129pub fn intercept_write(path: &str, content: &str) -> Option<Result<(), String>> {
130 with_world(|w| {
131 if w.caps.filesystem {
132 return None;
133 }
134 w.files.insert(path.to_string(), content.to_string());
135 Some(Ok(()))
136 })
137 .flatten()
138}
139
140pub fn intercept_env_get(key: &str) -> Option<Option<String>> {
141 with_world(|w| {
142 if w.caps.environment {
143 return None;
144 }
145 Some(w.env.get(key).cloned())
146 })
147 .flatten()
148}
149
150pub fn intercept_now() -> Option<u64> {
151 with_world(|w| if w.caps.clock { None } else { Some(w.clock_ms) }).flatten()
152}
153
154pub fn intercept_sleep(ms: u64) -> bool {
157 with_world(|w| {
158 if w.caps.clock {
159 return false;
160 }
161 w.clock_ms = w.clock_ms.saturating_add(ms);
162 true
163 })
164 .unwrap_or(false)
165}
166
167pub fn intercept_random_int(min: i64, max: i64) -> Option<i64> {
171 with_world(|w| {
172 if max <= min {
173 return min;
174 }
175 let span = max.wrapping_sub(min) as u64 as u128 + 1;
176 if span == 1 << 64 {
177 return w.next_u64() as i64;
178 }
179 let span = span as u64;
180 let biased_tail = (u64::MAX % span + 1) % span;
181 loop {
182 let r = w.next_u64();
183 if r <= u64::MAX - biased_tail {
184 return min.wrapping_add((r % span) as i64);
185 }
186 }
187 })
188}
189
190pub fn intercept_random_float() -> Option<f64> {
191 with_world(|w| (w.next_u64() >> 11) as f64 / (1u64 << 53) as f64)
192}