use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use crate::memo::{memo_scope, with_memo_scope};
use crate::memoize;
static COUNTER_SC1: AtomicUsize = AtomicUsize::new(0);
#[memoize]
async fn load_sc1(id: u32) -> u32 {
COUNTER_SC1.fetch_add(1, Ordering::SeqCst);
id * 2
}
#[tokio::test]
async fn memoize_runs_body_once_same_args() {
COUNTER_SC1.store(0, Ordering::SeqCst);
let store = memo_scope();
let (a, b, c) = with_memo_scope(store, async {
let a = load_sc1(1).await;
let b = load_sc1(1).await; let c = load_sc1(2).await; (a, b, c)
})
.await;
assert_eq!(a, 2);
assert_eq!(b, 2); assert_eq!(c, 4);
assert_eq!(COUNTER_SC1.load(Ordering::SeqCst), 2);
}
static COUNTER_SC2: AtomicUsize = AtomicUsize::new(0);
#[memoize]
async fn load_sc2(id: u32) -> u32 {
COUNTER_SC2.fetch_add(1, Ordering::SeqCst);
id * 3
}
#[tokio::test]
async fn concurrent_callers_coalesce() {
COUNTER_SC2.store(0, Ordering::SeqCst);
let store = memo_scope();
let (r1, r2) = with_memo_scope(store, async { tokio::join!(load_sc2(1), load_sc2(1)) }).await;
assert_eq!(r1, 3);
assert_eq!(r2, 3);
assert_eq!(COUNTER_SC2.load(Ordering::SeqCst), 1);
}
static COUNTER_METHOD: AtomicUsize = AtomicUsize::new(0);
struct ProductLoader;
impl ProductLoader {
#[memoize]
async fn load(&self, id: u32) -> u32 {
COUNTER_METHOD.fetch_add(1, Ordering::SeqCst);
id * 10
}
}
#[tokio::test]
async fn service_method_memoized() {
COUNTER_METHOD.store(0, Ordering::SeqCst);
let loader_a = Arc::new(ProductLoader);
let loader_b = Arc::new(ProductLoader);
let store = memo_scope();
let (r1, r2) = with_memo_scope(store, async {
let r1 = loader_a.load(7).await;
let r2 = loader_b.load(7).await;
(r1, r2)
})
.await;
assert_eq!(r1, 70);
assert_eq!(r2, 70); assert_eq!(COUNTER_METHOD.load(Ordering::SeqCst), 1);
}
static COUNTER_D02: AtomicUsize = AtomicUsize::new(0);
#[memoize]
async fn load_d02(id: u32) -> u32 {
COUNTER_D02.fetch_add(1, Ordering::SeqCst);
id + 100
}
#[tokio::test]
async fn out_of_scope_is_noop() {
COUNTER_D02.store(0, Ordering::SeqCst);
let r1 = load_d02(5).await;
let r2 = load_d02(5).await;
assert_eq!(r1, 105);
assert_eq!(r2, 105);
assert_eq!(COUNTER_D02.load(Ordering::SeqCst), 2);
}
static COUNTER_D04: AtomicUsize = AtomicUsize::new(0);
#[memoize]
async fn maybe(id: u32) -> Result<u32, String> {
COUNTER_D04.fetch_add(1, Ordering::SeqCst);
if id == 0 {
Err(format!("boom-{id}"))
} else {
Ok(id * 2)
}
}
#[tokio::test]
async fn err_is_cached() {
COUNTER_D04.store(0, Ordering::SeqCst);
let store = memo_scope();
let (r1, r2) = with_memo_scope(store, async {
let r1 = maybe(0).await;
let r2 = maybe(0).await; (r1, r2)
})
.await;
assert!(r1.is_err());
assert_eq!(r1.unwrap_err(), "boom-0");
assert!(r2.is_err());
assert_eq!(r2.unwrap_err(), "boom-0");
assert_eq!(COUNTER_D04.load(Ordering::SeqCst), 1);
}
static COUNTER_OWNED: AtomicUsize = AtomicUsize::new(0);
#[memoize]
async fn load_owned(name: String) -> usize {
COUNTER_OWNED.fetch_add(1, Ordering::SeqCst);
name.len()
}
#[tokio::test]
async fn memoize_supports_non_copy_args() {
COUNTER_OWNED.store(0, Ordering::SeqCst);
let store = memo_scope();
let (a, b, c) = with_memo_scope(store, async {
let a = load_owned("apple".to_string()).await;
let b = load_owned("apple".to_string()).await; let c = load_owned("kiwi".to_string()).await; (a, b, c)
})
.await;
assert_eq!(a, 5);
assert_eq!(b, 5); assert_eq!(c, 4);
assert_eq!(COUNTER_OWNED.load(Ordering::SeqCst), 2);
}