1pub mod repr;
5pub mod cache;
6
7#[cfg(feature = "eager")]
8pub use cache::eager::EagerCacheLookup;
9pub use cache::CacheableRepr;
10pub use repr::Repr;
11
12#[cfg(test)]
13mod tests {
14 use std::borrow::Cow;
15 use crate::repr::Repr;
16 use crate::CacheableRepr;
17 use std::cell::RefCell;
18 use std::collections::HashMap;
19 use std::rc::Rc;
20 use std::sync::{Arc};
21 use tokio::sync::RwLock;
22
23 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
24 struct MinMax {
25 min: i32,
26 max: i32,
27 }
28
29 #[test]
30 fn reading() {
31 let repr = Repr::new(
32 MinMax { min: 1, max: 5 },
33 |mm| mm.min < mm.max,
34 );
35 assert_eq!(repr.read().min, 1);
36 assert_eq!(repr.read().max, 5);
37 }
38
39 #[test]
40 fn reading_as_ref() {
41 let repr = Repr::new(
42 MinMax { min: 1, max: 5 },
43 |mm| mm.min < mm.max,
44 );
45 fn get_min<M: AsRef<MinMax>>(x: M) -> i32 { x.as_ref().min }
46 assert_eq!(get_min(&repr), 1);
47 assert_eq!(repr.read().max, 5);
48 }
49
50 #[test]
51 fn allowed_mutation() {
52 let mut repr = Repr::new(
53 MinMax { min: 1, max: 5 },
54 |mm| mm.min < mm.max,
55 );
56 let a = repr.read().min;
57 repr.write().min = 4;
58 assert_eq!(4, repr.read().min);
59 assert_eq!(1, a);
60 }
61
62 #[test]
63 #[should_panic]
64 fn should_propagate_panic() {
65 let mut repr = Repr::new(
66 MinMax { min: 1, max: 5 },
67 |mm| {
68 if mm.max <= 5 {
69 mm.min < mm.max
70 } else {
71 panic!("random panic")
72 }
73 },
74 );
75 let a = repr.read().min;
76 {
77 repr.write().min = 4;
78 assert_eq!(4, repr.read().min);
79 assert_eq!(1, a);
80 }
81 repr.write().max = 10;
82 }
83
84 #[test]
85 #[should_panic]
86 fn banned_mutation() {
87 let mut repr = Repr::new(
88 MinMax { min: 1, max: 5 },
89 |mm| mm.min < mm.max,
90 );
91 repr.write().min = 6;
92 }
93
94 #[test]
95 #[should_panic]
96 fn should_try_to_detect_non_deterministic_invariants() {
97 let value = Rc::new(RefCell::new(true));
98 let mut repr = Repr::new(
99 value.clone(),
100 |_| {
101 let res = *value.borrow();
102 value.replace(false);
103 res
104 },
105 );
106 repr.write();
107 }
108
109 #[test]
110 #[should_panic]
111 fn banned_mutation_with_msg() {
112 let mut repr = Repr::with_msg(
113 MinMax { min: 1, max: 5 },
114 |mm| mm.min < mm.max,
115 "min must always be less than max!",
116 );
117 repr.write().min = 6;
118 }
119
120 #[test]
121 fn should_work_with_borrowed_data() {
122 #[derive(Debug)]
123 struct Person<'a> {
124 name: Cow<'a, str>,
125 age: u8,
126 }
127 impl Person<'_> {
128 fn is_valid(&self) -> bool {
129 !self.name.is_empty() && self.age < 200
130 }
131 }
132 let bob = String::from("Bob");
133 let mut repr = Repr::with_msg(
134 Person {
135 name: bob.as_str().into(),
136 age: 25,
137 },
138 Person::is_valid,
139 "People must have a name and cannot be older than 200",
140 );
141 {
142 let person = repr.read();
143 assert_eq!("Bob", person.name);
144 assert_eq!(25, person.age);
145 }
146 {
147 let mut person = repr.write();
148 person.name = "Alice".into();
149 person.age = 30;
150 }
151 {
152 let person = repr.read();
153 assert_eq!("Alice", person.name);
154 assert_eq!(30, person.age);
155 }
156 }
157 #[test]
158 #[should_panic]
159 fn should_work_with_borrowed_data_doing_a_bad_mutation() {
160 #[derive(Debug)]
161 struct Person<'a> {
162 name: Cow<'a, str>,
163 age: u8,
164 }
165 impl Person<'_> {
166 fn is_valid(&self) -> bool {
167 !self.name.is_empty() && self.age < 200
168 }
169 }
170 let bob = String::from("Bob");
171 let mut repr = Repr::with_msg(
172 Person {
173 name: bob.as_str().into(),
174 age: 25,
175 },
176 Person::is_valid,
177 "People must have a name and cannot be older than 200",
178 );
179 {
180 let person = repr.read();
181 assert_eq!("Bob", person.name);
182 assert_eq!(25, person.age);
183 }
184 {
185 let mut person = repr.write();
186 person.name = "Alice".into();
187 person.age = 200;
188 }
189 {
190 let person = repr.read();
191 assert_eq!("Alice", person.name);
192 assert_eq!(30, person.age);
193 }
194 }
195
196 #[test]
197 fn should_read_from_cache() {
198 let mut repr = CacheableRepr::new(
199 MinMax { min: 1, max: 5 },
200 |mm| mm.min < mm.max,
201 );
202 fn get_min(mm: &MinMax) -> i32 { mm.min }
203 assert_eq!(1, repr.lazy(get_min));
204 assert_eq!(1, repr.lazy(get_min));
205 }
206 #[test]
207 fn should_invalidate_cache_on_mutation() {
208 let mut repr = CacheableRepr::new(
209 MinMax { min: 1, max: 5 },
210 |mm| mm.min < mm.max,
211 );
212 fn get_min(mm: &MinMax) -> i32 {
213 mm.min
214 }
215 assert_eq!(1, repr.lazy(get_min));
216 assert_eq!(1, repr.lazy(get_min));
217 repr.write().min = 4;
218 assert_eq!(4, repr.lazy(get_min));
219 assert_eq!(4, repr.lazy(get_min));
220 }
221
222 #[test]
223 fn should_allow_static_closures_for_cache_reads() {
224 let mut repr = CacheableRepr::new(
225 MinMax { min: 1, max: 5 },
226 |mm| mm.min < mm.max,
227 );
228 assert_eq!(1, repr.lazy(|mm| mm.min));
229 assert_eq!(1, repr.lazy(|mm| mm.min));
230 repr.write().min = 4;
231 assert_eq!(4, repr.lazy(|mm| mm.min));
232 assert_eq!(4, repr.lazy(|mm| mm.min));
233 }
234
235 #[test]
236 fn should_hash_by_inner() {
237 let mut repr1 = Repr::new(
238 MinMax { min: 1, max: 5 },
239 |mm| mm.min < mm.max,
240 );
241 let mut repr2 = Repr::new(
242 MinMax { min: 1, max: 5 },
243 |mm| mm.min < mm.max,
244 );
245 let mut map = HashMap::new();
246 let repr1_w: &mut MinMax = &mut repr1.write();
247 let repr2_w: &mut MinMax = &mut repr2.write();
248 map.insert(&repr1_w, 1);
249 assert!(map.contains_key(&repr1_w));
250 assert!(map.contains_key(&repr2_w));
251 repr2_w.max = 10;
252 assert!(map.contains_key(&repr1_w));
253 assert!(!map.contains_key(&repr2_w));
254 }
255
256 #[test]
257 fn should_be_moveable_across_threads() {
258 let mut repr = Repr::new(
259 MinMax { min: 1, max: 5 },
260 |mm| mm.min < mm.max,
261 );
262 fn get_min(mm: &MinMax) -> i32 { mm.min }
263 fn get_max(mm: &MinMax) -> i32 { mm.max }
264
265 {
266 let mm = repr.as_ref();
267 assert_eq!(1, get_min(mm));
268 assert_eq!(5, get_max(mm));
269 }
270 {
271 let mut mm = repr.write();
272 mm.max = 100;
273 mm.min = 50;
274 }
275 std::thread::spawn(move || {
276 let mm = repr.as_ref();
277 assert_eq!(50, get_min(mm));
278 assert_eq!(100, get_max(mm));
279 }).join().unwrap();
280 }
281
282 #[tokio::test(flavor = "multi_thread")]
283 async fn should_be_moveable_across_tasks() {
284 let mut repr = Repr::new(
285 MinMax { min: 1, max: 5 },
286 |mm| mm.min < mm.max,
287 );
288 fn get_min(mm: &MinMax) -> i32 { mm.min }
289 fn get_max(mm: &MinMax) -> i32 { mm.max }
290
291 {
292 let mm = repr.as_ref();
293 assert_eq!(1, get_min(mm));
294 assert_eq!(5, get_max(mm));
295 }
296 {
297 let mut mm = repr.write();
298 mm.max = 100;
299 mm.min = 50;
300 }
301 tokio::spawn(async move {
302 let mm = repr.as_ref();
303 assert_eq!(50, get_min(mm));
304 assert_eq!(100, get_max(mm));
305 }).await.unwrap();
306 }
307
308 #[tokio::test(flavor = "multi_thread")]
309 async fn should_be_shareable_across_threads() {
310 let mut repr = Repr::new(
311 MinMax { min: 1, max: 5 },
312 |mm| mm.min < mm.max,
313 );
314 fn get_min(mm: &MinMax) -> i32 { mm.min }
315 fn get_max(mm: &MinMax) -> i32 { mm.max }
316
317 {
318 let mm = repr.as_ref();
319 assert_eq!(1, get_min(mm));
320 assert_eq!(5, get_max(mm));
321 }
322 {
323 let mut mm = repr.write();
324 mm.max = 100;
325 mm.min = 50;
326 }
327 std::thread::scope(|s| {
328 s.spawn(|| {
329 let mm = repr.as_ref();
330 assert_eq!(50, get_min(mm));
331 assert_eq!(100, get_max(mm));
332 });
333 });
334 }
335
336 #[tokio::test(flavor = "multi_thread")]
337 async fn should_work_with_shared_ownership_and_rwlock() {
338 let repr = Arc::new(RwLock::new(Repr::new(
339 MinMax { min: 1, max: 5 },
340 |mm| mm.min < mm.max,
341 )));
342 fn get_min(mm: &MinMax) -> i32 { mm.min }
343 fn get_max(mm: &MinMax) -> i32 { mm.max }
344
345 {
346 let lock = repr.read().await;
347 let mm = lock.read();
348 assert_eq!(1, get_min(mm));
349 assert_eq!(5, get_max(mm));
350 }
351 {
352 let mut lock = repr.write().await;
353 let mut mm = lock.write();
354 mm.max = 100;
355 mm.min = 50;
356 }
357 let r = repr.clone();
358 tokio::spawn(async move {
359 let mut lock = r.write().await;
360 let mut mm = lock.write();
361 assert_eq!(50, get_min(&mm));
362 assert_eq!(100, get_max(&mm));
363 mm.min = 10;
364 mm.max = 20;
365 }).await.unwrap();
366 {
367 let lock = repr.read().await;
368 let mm = lock.read();
369 assert_eq!(10, get_min(mm));
370 assert_eq!(20, get_max(mm));
371 }
372 }
373
374 #[cfg(feature = "eager")]
375 mod eager {
376 use std::sync::atomic::{AtomicU32, Ordering};
377 use std::time::Duration;
378 use crate::tests::MinMax;
379 use crate::{CacheableRepr, EagerCacheLookup};
380
381 #[tokio::test(flavor = "multi_thread")]
382 async fn should_read_from_cache() {
383 let mut repr = CacheableRepr::new(
384 MinMax { min: 1, max: 5 },
385 |mm| mm.min < mm.max,
386 );
387 fn get_min(mm: &MinMax) -> i32 {
388 mm.min
389 }
390 assert_eq!(1, repr.eager(get_min).await);
391 assert_eq!(1, repr.eager(get_min).await);
392 }
394 #[tokio::test(flavor = "multi_thread")]
395 async fn should_invalidate_cache_on_mutation() {
396 let mut repr = CacheableRepr::new(
397 MinMax { min: 1, max: 5 },
398 |mm| mm.min < mm.max,
399 );
400 fn get_min(mm: &MinMax) -> i32 {
401 mm.min
402 }
403 assert_eq!(1, repr.eager(get_min).await);
404 assert_eq!(1, repr.eager(get_min).await);
405 repr.write().min = 4;
406 assert_eq!(4, repr.eager(get_min).await);
407 assert_eq!(4, repr.eager(get_min).await);
408 }
409
410 #[tokio::test(flavor = "multi_thread")]
411 async fn should_allow_static_closures_for_cache_reads() {
412 let mut repr = CacheableRepr::new(
413 MinMax { min: 1, max: 5 },
414 |mm| mm.min < mm.max,
415 );
416 assert_eq!(1, repr.eager(|mm| mm.min).await);
417 assert_eq!(1, repr.eager(|mm| mm.min).await);
418 repr.write().min = 4;
419 assert_eq!(4, repr.eager(|mm| mm.min).await);
420 assert_eq!(4, repr.eager(|mm| mm.min).await);
421 }
422
423 #[ignore]
424 #[tokio::test(flavor = "multi_thread")]
425 async fn should_work_with_expensive_computations() {
426 let mut repr = CacheableRepr::new(
427 MinMax { min: 1, max: 40 },
428 |mm| mm.min < mm.max,
429 );
430
431 fn fib(n: u64) -> u64 {
432 if n <= 1 {
433 return n;
434 }
435 fib(n - 1) + fib(n - 2)
436 }
437 fn plain_fib(mm: &MinMax) -> u64 { fib(mm.max as u64) }
438 fn plus2(mm: &MinMax) -> u64 { fib((mm.max + 2) as u64) }
439 fn plus3(mm: &MinMax) -> u64 { fib((mm.max + 3) as u64) }
440 fn plus4(mm: &MinMax) -> u64 { fib((mm.max + 4) as u64) }
441 fn plus5(mm: &MinMax) -> u64 { fib((mm.max + 5) as u64) }
442
443 assert_eq!(102334155, repr.eager(plain_fib).await);
444 assert_eq!(267914296, repr.eager(plus2).await);
445 assert_eq!(433494437, repr.eager(plus3).await);
446 assert_eq!(701408733, repr.eager(plus4).await);
447 assert_eq!(1134903170, repr.eager(plus5).await);
448 repr.write().max = 42;
449 assert_eq!(267914296, repr.eager(plain_fib).await);
450 }
451
452 #[tokio::test(flavor = "multi_thread")]
453 async fn should_be_able_to_unregister_caches() {
454 let mut repr = CacheableRepr::new(
455 MinMax { min: 1, max: 5 },
456 |mm| mm.min < mm.max,
457 );
458 fn get_min(mm: &MinMax) -> i32 {
459 mm.min
460 }
461 fn get_min2(mm: &MinMax) -> i32 {
462 mm.min
463 }
464 assert_eq!(1, repr.eager(get_min).await);
465 assert_eq!(1, repr.eager(get_min2).await);
466 assert_eq!(1, repr.eager(get_min).await);
467 assert!(repr.unregister(get_min2));
468 }
469
470 #[ignore]
471 #[tokio::test(flavor = "multi_thread")]
472 #[should_panic]
473 async fn should_propagate_panic_in_eager_cache() {
474 let mut repr = CacheableRepr::new(
475 MinMax { min: 1, max: 5 },
476 |mm| mm.min < mm.max,
477 );
478 fn get_min(mm: &MinMax) -> i32 {
479 mm.min
480 }
481 fn get_min2(mm: &MinMax) -> i32 {
482 if mm.min == 1 {
483 mm.min
484 } else {
485 panic!("random panic")
486 }
487 }
488 assert_eq!(1, repr.eager(get_min).await);
489 assert_eq!(1, repr.eager(get_min2).await);
490 assert_eq!(1, repr.eager(get_min).await);
491 repr.write().min = 2;
492 }
493
494 #[tokio::test(flavor = "multi_thread")]
495 async fn counting_eager_cache_hits() {
496 #[derive(Debug, Clone)]
497 struct Person { name: String }
498 let mut repr = CacheableRepr::new(Person { name: "Alice and Bob together at last".into() }, |p| !p.name.is_empty());
499 static READ_SPY: AtomicU32 = AtomicU32::new(0);
500 fn expensive_read(p: &Person) -> usize {
501 READ_SPY.fetch_add(1, Ordering::Relaxed);
504 fib(p.name.len())
505 }
506 let fib_of_name_len = repr.eager(expensive_read).await;
507 assert_eq!(832040, fib_of_name_len);
508 let fib_of_name_len2 = repr.eager(expensive_read).await;
510 assert_eq!(832040, fib_of_name_len2);
511 assert_eq!(1, READ_SPY.load(Ordering::Relaxed));
512 repr.write().name = "Alice".into();
513 tokio::time::sleep(Duration::from_millis(100)).await;
515 assert_eq!(2, READ_SPY.load(Ordering::Relaxed));
516 let fib_of_name_len3 = repr.eager(expensive_read).await;
518 assert_eq!(5, fib_of_name_len3);
519 assert_eq!(2, READ_SPY.load(Ordering::Relaxed));
520 fn fib(n: usize) -> usize {
521 if n <= 1 { n } else { fib(n - 1) + fib(n - 2) }
522 }
523 }
524 }
525}