#![cfg(feature = "proc_macro")]
#![allow(clippy::ptr_arg)]
use cached::macros::{cached, concurrent_cached, once};
static COLLIDE_CACHED_CALLS: AtomicUsize = AtomicUsize::new(0);
static COLLIDE_ONCE_CALLS: AtomicUsize = AtomicUsize::new(0);
static COLLIDE_CONCURRENT_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached]
fn collide_cached(key: i32, cache: i32, result: i32) -> i32 {
COLLIDE_CACHED_CALLS.fetch_add(1, Ordering::SeqCst);
key + cache + result
}
#[once]
fn collide_once(key: i32, cache: i32, result: i32) -> i32 {
COLLIDE_ONCE_CALLS.fetch_add(1, Ordering::SeqCst);
key + cache + result
}
#[concurrent_cached]
fn collide_concurrent(key: i32, cache: i32, result: i32) -> i32 {
COLLIDE_CONCURRENT_CALLS.fetch_add(1, Ordering::SeqCst);
key + cache + result
}
#[test]
fn arg_name_collisions_compile_and_cache() {
COLLIDE_CACHED_CALLS.store(0, Ordering::SeqCst);
COLLIDE_ONCE_CALLS.store(0, Ordering::SeqCst);
COLLIDE_CONCURRENT_CALLS.store(0, Ordering::SeqCst);
assert_eq!(collide_cached(1, 2, 3), 6);
assert_eq!(collide_cached(1, 2, 3), 6); assert_eq!(
COLLIDE_CACHED_CALLS.load(Ordering::SeqCst),
1,
"#[cached]: second same-arg call must be a cache hit (body runs once)"
);
assert_eq!(collide_cached(10, 20, 30), 60);
assert_eq!(collide_once(1, 2, 3), 6);
assert_eq!(collide_once(4, 5, 6), 6); assert_eq!(
COLLIDE_ONCE_CALLS.load(Ordering::SeqCst),
1,
"#[once]: second call with different args must be a cache hit (body runs once)"
);
assert_eq!(collide_concurrent(1, 2, 3), 6);
assert_eq!(collide_concurrent(1, 2, 3), 6);
assert_eq!(
COLLIDE_CONCURRENT_CALLS.load(Ordering::SeqCst),
1,
"#[concurrent_cached]: second same-arg call must be a cache hit (body runs once)"
);
assert_eq!(collide_concurrent(7, 8, 9), 24);
}
use std::sync::atomic::{AtomicUsize, Ordering};
static STR_CALLS: AtomicUsize = AtomicUsize::new(0);
static OPT_CALLS: AtomicUsize = AtomicUsize::new(0);
static STRING_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached]
fn ref_str_len(s: &str) -> usize {
STR_CALLS.fetch_add(1, Ordering::SeqCst);
s.len()
}
#[cached]
fn opt_ref_str_len(o: Option<&str>) -> usize {
OPT_CALLS.fetch_add(1, Ordering::SeqCst);
o.map_or(0, |s| s.len())
}
#[cached]
fn ref_string_len(s: &String) -> usize {
STRING_CALLS.fetch_add(1, Ordering::SeqCst);
s.len()
}
#[test]
fn reference_inputs_default_key() {
STR_CALLS.store(0, Ordering::SeqCst);
OPT_CALLS.store(0, Ordering::SeqCst);
STRING_CALLS.store(0, Ordering::SeqCst);
assert_eq!(ref_str_len("hello"), 5);
assert_eq!(ref_str_len("hello"), 5);
assert_eq!(
STR_CALLS.load(Ordering::SeqCst),
1,
"second call should hit cache"
);
assert_eq!(ref_str_len("hi"), 2);
assert_eq!(STR_CALLS.load(Ordering::SeqCst), 2);
assert_eq!(opt_ref_str_len(Some("hello")), 5);
assert_eq!(opt_ref_str_len(Some("hello")), 5);
assert_eq!(OPT_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(opt_ref_str_len(None), 0);
assert_eq!(opt_ref_str_len(None), 0);
assert_eq!(OPT_CALLS.load(Ordering::SeqCst), 2);
let owned = String::from("world!");
assert_eq!(ref_string_len(&owned), 6);
assert_eq!(ref_string_len(&owned), 6);
assert_eq!(STRING_CALLS.load(Ordering::SeqCst), 1);
}
static FORCE_CALLS: AtomicUsize = AtomicUsize::new(0);
static FORCE_SOURCE: AtomicUsize = AtomicUsize::new(1);
#[cached(key = "i32", convert = "{ x }", force_refresh = "{ bypass }")]
fn force_refresh_fn(x: i32, bypass: bool) -> usize {
let _ = bypass; FORCE_CALLS.fetch_add(1, Ordering::SeqCst);
x as usize + FORCE_SOURCE.load(Ordering::SeqCst)
}
#[test]
fn force_refresh_bypasses_cache() {
FORCE_CALLS.store(0, Ordering::SeqCst);
FORCE_SOURCE.store(1, Ordering::SeqCst);
let first = force_refresh_fn(1, false); assert_eq!(first, 2);
assert_eq!(FORCE_CALLS.load(Ordering::SeqCst), 1);
FORCE_SOURCE.store(100, Ordering::SeqCst);
let hit = force_refresh_fn(1, false);
assert_eq!(hit, 2, "served the stale cached value");
assert_eq!(FORCE_CALLS.load(Ordering::SeqCst), 1);
let refreshed = force_refresh_fn(1, true);
assert_eq!(refreshed, 101, "recomputed against the new source");
assert_eq!(FORCE_CALLS.load(Ordering::SeqCst), 2);
let after = force_refresh_fn(1, false);
assert_eq!(after, 101, "force_refresh overwrote the cache entry");
assert_eq!(FORCE_CALLS.load(Ordering::SeqCst), 2);
}
static FORCE_CONC_CALLS: AtomicUsize = AtomicUsize::new(0);
static FORCE_CONC_SOURCE: AtomicUsize = AtomicUsize::new(1);
#[concurrent_cached(key = "i32", convert = "{ x }", force_refresh = "{ bypass }")]
fn force_refresh_concurrent(x: i32, bypass: bool) -> usize {
let _ = bypass; FORCE_CONC_CALLS.fetch_add(1, Ordering::SeqCst);
x as usize + FORCE_CONC_SOURCE.load(Ordering::SeqCst)
}
#[test]
fn force_refresh_concurrent_bypasses_cache() {
FORCE_CONC_CALLS.store(0, Ordering::SeqCst);
FORCE_CONC_SOURCE.store(1, Ordering::SeqCst);
let first = force_refresh_concurrent(2, false); assert_eq!(first, 3);
assert_eq!(FORCE_CONC_CALLS.load(Ordering::SeqCst), 1);
FORCE_CONC_SOURCE.store(100, Ordering::SeqCst);
let hit = force_refresh_concurrent(2, false);
assert_eq!(hit, 3, "served the stale cached value");
assert_eq!(FORCE_CONC_CALLS.load(Ordering::SeqCst), 1);
let refreshed = force_refresh_concurrent(2, true);
assert_eq!(refreshed, 102, "recomputed against the new source");
assert_eq!(FORCE_CONC_CALLS.load(Ordering::SeqCst), 2);
let after = force_refresh_concurrent(2, false);
assert_eq!(after, 102, "force_refresh overwrote the cache entry");
assert_eq!(FORCE_CONC_CALLS.load(Ordering::SeqCst), 2);
}
static FORCE_EXPR_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(force_refresh = "{ x == 0 }")]
fn force_refresh_expr(x: i32) -> usize {
FORCE_EXPR_CALLS.fetch_add(1, Ordering::SeqCst);
x as usize
}
#[test]
fn force_refresh_expression_over_args() {
FORCE_EXPR_CALLS.store(0, Ordering::SeqCst);
let _ = force_refresh_expr(0);
let _ = force_refresh_expr(0);
assert_eq!(
FORCE_EXPR_CALLS.load(Ordering::SeqCst),
2,
"x==0 bypasses every call"
);
let _ = force_refresh_expr(5);
let _ = force_refresh_expr(5);
assert_eq!(
FORCE_EXPR_CALLS.load(Ordering::SeqCst),
3,
"x!=0 served from cache"
);
}
static FOOTGUN_CALLS: AtomicUsize = AtomicUsize::new(0);
static FOOTGUN_SOURCE: AtomicUsize = AtomicUsize::new(1);
#[cached(force_refresh = "{ refresh }")]
fn force_refresh_default_key(x: i32, refresh: bool) -> usize {
let _ = refresh;
FOOTGUN_CALLS.fetch_add(1, Ordering::SeqCst);
x as usize + FOOTGUN_SOURCE.load(Ordering::SeqCst)
}
#[test]
fn force_refresh_default_key_does_not_update_normal_slot() {
FOOTGUN_CALLS.store(0, Ordering::SeqCst);
FOOTGUN_SOURCE.store(1, Ordering::SeqCst);
assert_eq!(force_refresh_default_key(1, false), 2); FOOTGUN_SOURCE.store(100, Ordering::SeqCst);
assert_eq!(force_refresh_default_key(1, true), 101);
assert_eq!(
force_refresh_default_key(1, false),
2,
"default key: forced refresh writes a separate (x,true) slot, not seen here"
);
assert_eq!(
FOOTGUN_CALLS.load(Ordering::SeqCst),
2,
"body ran exactly twice: once for the initial miss and once for the force-refresh"
);
}
static ONCE_FR_CALLS: AtomicUsize = AtomicUsize::new(0);
static ONCE_FR_SOURCE: AtomicUsize = AtomicUsize::new(10);
#[once(force_refresh = "{ bypass }")]
fn once_force_refresh(bypass: bool) -> usize {
let _ = bypass; ONCE_FR_CALLS.fetch_add(1, Ordering::SeqCst);
ONCE_FR_SOURCE.load(Ordering::SeqCst)
}
#[test]
fn once_force_refresh_recomputes_shared_value() {
ONCE_FR_CALLS.store(0, Ordering::SeqCst);
ONCE_FR_SOURCE.store(10, Ordering::SeqCst);
let first = once_force_refresh(false);
assert_eq!(first, 10);
assert_eq!(ONCE_FR_CALLS.load(Ordering::SeqCst), 1);
ONCE_FR_SOURCE.store(99, Ordering::SeqCst); let hit = once_force_refresh(false);
assert_eq!(hit, first, "cached hit, body not re-run");
assert_eq!(ONCE_FR_CALLS.load(Ordering::SeqCst), 1);
let refreshed = once_force_refresh(true);
assert_eq!(ONCE_FR_CALLS.load(Ordering::SeqCst), 2);
assert_eq!(
refreshed, 99,
"force_refresh recomputed against the new source"
);
assert_ne!(refreshed, first, "force_refresh produced a new value");
let after = once_force_refresh(false);
assert_eq!(after, refreshed, "later calls see the overwritten value");
assert_eq!(ONCE_FR_CALLS.load(Ordering::SeqCst), 2);
}
#[cfg(feature = "time_stores")]
mod force_refresh_result_fallback {
use super::*;
static FB_CALLS: AtomicUsize = AtomicUsize::new(0);
static FB_SOURCE: AtomicUsize = AtomicUsize::new(0);
#[cached(
key = "i32",
convert = "{ x }",
ttl_secs = 600,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn fb_fn(x: i32, bypass: bool) -> Result<usize, ()> {
let _ = bypass; FB_CALLS.fetch_add(1, Ordering::SeqCst);
match FB_SOURCE.load(Ordering::SeqCst) {
0 => Err(()),
v => Ok(x as usize + v),
}
}
#[test]
fn err_falls_back_force_refresh_recomputes_on_ok() {
FB_CALLS.store(0, Ordering::SeqCst);
FB_SOURCE.store(10, Ordering::SeqCst);
assert_eq!(fb_fn(1, false), Ok(11));
assert_eq!(FB_CALLS.load(Ordering::SeqCst), 1);
FB_SOURCE.store(0, Ordering::SeqCst); assert_eq!(fb_fn(1, false), Ok(11), "cached hit, body not re-run");
assert_eq!(FB_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(fb_fn(1, true), Ok(11), "Err refresh falls back to last Ok");
assert_eq!(FB_CALLS.load(Ordering::SeqCst), 2);
FB_SOURCE.store(50, Ordering::SeqCst);
assert_eq!(fb_fn(1, true), Ok(51), "Ok refresh recomputes + overwrites");
assert_eq!(FB_CALLS.load(Ordering::SeqCst), 3);
FB_SOURCE.store(0, Ordering::SeqCst);
assert_eq!(fb_fn(1, false), Ok(51), "serves the overwritten value");
assert_eq!(FB_CALLS.load(Ordering::SeqCst), 3);
}
static FRSE_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(
name = "FRSE_CACHE",
key = "i32",
convert = "{ x }",
ttl_secs = 600,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn frse_fn(x: i32, bypass: bool) -> Result<usize, ()> {
let _ = bypass; FRSE_CALLS.fetch_add(1, Ordering::SeqCst);
Ok(x as usize + 1)
}
#[test]
fn force_refresh_bypass_has_no_read_side_effects() {
use cached::Cached;
FRSE_CALLS.store(0, Ordering::SeqCst);
assert_eq!(frse_fn(1, false), Ok(2));
assert_eq!(FRSE_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(
FRSE_CACHE.read().cache_hits(),
Some(0),
"seeding the entry is a miss + set, not a hit"
);
assert_eq!(frse_fn(1, true), Ok(2));
assert_eq!(frse_fn(1, true), Ok(2));
assert_eq!(frse_fn(1, true), Ok(2));
assert_eq!(
FRSE_CALLS.load(Ordering::SeqCst),
4,
"each bypass recomputes"
);
assert_eq!(
FRSE_CACHE.read().cache_hits(),
Some(0),
"force_refresh bypass must not hit-count the bypassed entry (#146)"
);
assert_eq!(frse_fn(1, false), Ok(2));
assert_eq!(
FRSE_CALLS.load(Ordering::SeqCst),
4,
"non-bypass served from cache"
);
assert_eq!(
FRSE_CACHE.read().cache_hits(),
Some(1),
"a real early-return hit increments the counter"
);
}
static CFB_CALLS: AtomicUsize = AtomicUsize::new(0);
static CFB_SOURCE: AtomicUsize = AtomicUsize::new(0);
#[concurrent_cached(
key = "i32",
convert = "{ x }",
ttl_secs = 600,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn cfb_fn(x: i32, bypass: bool) -> Result<usize, ()> {
let _ = bypass; CFB_CALLS.fetch_add(1, Ordering::SeqCst);
match CFB_SOURCE.load(Ordering::SeqCst) {
0 => Err(()),
v => Ok(x as usize + v),
}
}
#[test]
fn concurrent_err_falls_back_force_refresh_recomputes_on_ok() {
CFB_CALLS.store(0, Ordering::SeqCst);
CFB_SOURCE.store(10, Ordering::SeqCst);
assert_eq!(cfb_fn(1, false), Ok(11));
assert_eq!(CFB_CALLS.load(Ordering::SeqCst), 1);
CFB_SOURCE.store(0, Ordering::SeqCst); assert_eq!(cfb_fn(1, false), Ok(11), "cached hit, body not re-run");
assert_eq!(CFB_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(cfb_fn(1, true), Ok(11), "Err refresh falls back to last Ok");
assert_eq!(CFB_CALLS.load(Ordering::SeqCst), 2);
CFB_SOURCE.store(50, Ordering::SeqCst);
assert_eq!(
cfb_fn(1, true),
Ok(51),
"Ok refresh recomputes + overwrites"
);
assert_eq!(CFB_CALLS.load(Ordering::SeqCst), 3);
CFB_SOURCE.store(0, Ordering::SeqCst);
assert_eq!(cfb_fn(1, false), Ok(51), "serves the overwritten value");
assert_eq!(CFB_CALLS.load(Ordering::SeqCst), 3);
}
static CFRSE_CALLS: AtomicUsize = AtomicUsize::new(0);
#[concurrent_cached(
name = "CFRSE_CACHE",
key = "i32",
convert = "{ x }",
ttl_secs = 600,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn cfrse_fn(x: i32, bypass: bool) -> Result<usize, ()> {
let _ = bypass; CFRSE_CALLS.fetch_add(1, Ordering::SeqCst);
Ok(x as usize + 1)
}
#[test]
fn concurrent_force_refresh_bypass_has_no_read_side_effects() {
CFRSE_CALLS.store(0, Ordering::SeqCst);
assert_eq!(cfrse_fn(1, false), Ok(2));
assert_eq!(CFRSE_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(
CFRSE_CACHE.metrics().hits,
Some(0),
"seeding the entry is a miss + set, not a hit"
);
assert_eq!(cfrse_fn(1, true), Ok(2));
assert_eq!(cfrse_fn(1, true), Ok(2));
assert_eq!(cfrse_fn(1, true), Ok(2));
assert_eq!(
CFRSE_CALLS.load(Ordering::SeqCst),
4,
"each bypass recomputes"
);
assert_eq!(
CFRSE_CACHE.metrics().hits,
Some(0),
"force_refresh bypass must not hit-count the bypassed entry (#146)"
);
assert_eq!(cfrse_fn(1, false), Ok(2));
assert_eq!(
CFRSE_CALLS.load(Ordering::SeqCst),
4,
"non-bypass served from cache"
);
assert_eq!(
CFRSE_CACHE.metrics().hits,
Some(1),
"a real early-return hit increments the counter"
);
}
struct ImplFallback;
static IMPL_FB_CALLS: AtomicUsize = AtomicUsize::new(0);
static IMPL_FB_SOURCE: AtomicUsize = AtomicUsize::new(0);
impl ImplFallback {
#[cached(
in_impl = true,
key = "i32",
convert = "{ x }",
ttl_secs = 600,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn fb_method(&self, x: i32, bypass: bool) -> Result<usize, ()> {
let _ = bypass; IMPL_FB_CALLS.fetch_add(1, Ordering::SeqCst);
match IMPL_FB_SOURCE.load(Ordering::SeqCst) {
0 => Err(()),
v => Ok(x as usize + v),
}
}
}
#[test]
fn in_impl_err_falls_back_force_refresh_recomputes_on_ok() {
IMPL_FB_CALLS.store(0, Ordering::SeqCst);
let s = ImplFallback;
IMPL_FB_SOURCE.store(10, Ordering::SeqCst);
assert_eq!(s.fb_method(1, false), Ok(11));
assert_eq!(IMPL_FB_CALLS.load(Ordering::SeqCst), 1);
IMPL_FB_SOURCE.store(0, Ordering::SeqCst); assert_eq!(s.fb_method(1, false), Ok(11), "cached hit, body not re-run");
assert_eq!(IMPL_FB_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(
s.fb_method(1, true),
Ok(11),
"Err refresh falls back to last Ok"
);
assert_eq!(IMPL_FB_CALLS.load(Ordering::SeqCst), 2);
IMPL_FB_SOURCE.store(50, Ordering::SeqCst);
assert_eq!(
s.fb_method(1, true),
Ok(51),
"Ok refresh recomputes + overwrites"
);
assert_eq!(IMPL_FB_CALLS.load(Ordering::SeqCst), 3);
IMPL_FB_SOURCE.store(0, Ordering::SeqCst);
assert_eq!(
s.fb_method(1, false),
Ok(51),
"serves the overwritten value"
);
assert_eq!(IMPL_FB_CALLS.load(Ordering::SeqCst), 3);
}
static FB_MILLIS_CALLS: AtomicUsize = AtomicUsize::new(0);
static FB_MILLIS_SOURCE: AtomicUsize = AtomicUsize::new(0);
#[cached(
key = "i32",
convert = "{ x }",
ttl_millis = 600_000,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn fb_millis_fn(x: i32, bypass: bool) -> Result<usize, ()> {
let _ = bypass; FB_MILLIS_CALLS.fetch_add(1, Ordering::SeqCst);
match FB_MILLIS_SOURCE.load(Ordering::SeqCst) {
0 => Err(()),
v => Ok(x as usize + v),
}
}
#[test]
fn err_falls_back_force_refresh_recomputes_on_ok_ttl_millis() {
FB_MILLIS_CALLS.store(0, Ordering::SeqCst);
FB_MILLIS_SOURCE.store(10, Ordering::SeqCst);
assert_eq!(fb_millis_fn(1, false), Ok(11));
assert_eq!(FB_MILLIS_CALLS.load(Ordering::SeqCst), 1);
FB_MILLIS_SOURCE.store(0, Ordering::SeqCst); assert_eq!(
fb_millis_fn(1, false),
Ok(11),
"cached hit, body not re-run"
);
assert_eq!(FB_MILLIS_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(
fb_millis_fn(1, true),
Ok(11),
"Err refresh falls back to last Ok"
);
assert_eq!(FB_MILLIS_CALLS.load(Ordering::SeqCst), 2);
FB_MILLIS_SOURCE.store(50, Ordering::SeqCst);
assert_eq!(
fb_millis_fn(1, true),
Ok(51),
"Ok refresh recomputes + overwrites"
);
assert_eq!(FB_MILLIS_CALLS.load(Ordering::SeqCst), 3);
FB_MILLIS_SOURCE.store(0, Ordering::SeqCst);
assert_eq!(
fb_millis_fn(1, false),
Ok(51),
"serves the overwritten value"
);
assert_eq!(FB_MILLIS_CALLS.load(Ordering::SeqCst), 3);
}
static CFB_MILLIS_CALLS: AtomicUsize = AtomicUsize::new(0);
static CFB_MILLIS_SOURCE: AtomicUsize = AtomicUsize::new(0);
#[concurrent_cached(
key = "i32",
convert = "{ x }",
ttl_millis = 600_000,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn cfb_millis_fn(x: i32, bypass: bool) -> Result<usize, ()> {
let _ = bypass; CFB_MILLIS_CALLS.fetch_add(1, Ordering::SeqCst);
match CFB_MILLIS_SOURCE.load(Ordering::SeqCst) {
0 => Err(()),
v => Ok(x as usize + v),
}
}
#[test]
fn concurrent_err_falls_back_force_refresh_recomputes_on_ok_ttl_millis() {
CFB_MILLIS_CALLS.store(0, Ordering::SeqCst);
CFB_MILLIS_SOURCE.store(10, Ordering::SeqCst);
assert_eq!(cfb_millis_fn(1, false), Ok(11));
assert_eq!(CFB_MILLIS_CALLS.load(Ordering::SeqCst), 1);
CFB_MILLIS_SOURCE.store(0, Ordering::SeqCst); assert_eq!(
cfb_millis_fn(1, false),
Ok(11),
"cached hit, body not re-run"
);
assert_eq!(CFB_MILLIS_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(
cfb_millis_fn(1, true),
Ok(11),
"Err refresh falls back to last Ok"
);
assert_eq!(CFB_MILLIS_CALLS.load(Ordering::SeqCst), 2);
CFB_MILLIS_SOURCE.store(50, Ordering::SeqCst);
assert_eq!(
cfb_millis_fn(1, true),
Ok(51),
"Ok refresh recomputes + overwrites"
);
assert_eq!(CFB_MILLIS_CALLS.load(Ordering::SeqCst), 3);
CFB_MILLIS_SOURCE.store(0, Ordering::SeqCst);
assert_eq!(
cfb_millis_fn(1, false),
Ok(51),
"serves the overwritten value"
);
assert_eq!(CFB_MILLIS_CALLS.load(Ordering::SeqCst), 3);
}
}
static COMPUTE_CALLS: AtomicUsize = AtomicUsize::new(0);
struct Calculator {
base: i32,
}
impl Calculator {
#[cached(in_impl = true)]
fn compute(&self, k: i32) -> i32 {
COMPUTE_CALLS.fetch_add(1, Ordering::SeqCst);
k * 2
}
}
#[test]
fn in_impl_self_method_caches() {
let c = Calculator { base: 100 };
assert_eq!(c.compute(5), 10);
assert_eq!(c.compute(5), 10);
assert_eq!(
COMPUTE_CALLS.load(Ordering::SeqCst),
1,
"second call should hit cache"
);
assert_eq!(c.compute(6), 12);
assert_eq!(COMPUTE_CALLS.load(Ordering::SeqCst), 2);
let other = Calculator { base: 0 };
assert_eq!(other.compute(5), 10);
assert_eq!(
COMPUTE_CALLS.load(Ordering::SeqCst),
2,
"shared cache: still a hit"
);
let _ = c.base + other.base; }
static CONC_METHOD_CALLS: AtomicUsize = AtomicUsize::new(0);
static ONCE_METHOD_CALLS: AtomicUsize = AtomicUsize::new(0);
struct Svc;
impl Svc {
#[concurrent_cached(in_impl = true)]
fn conc_method(&self, k: i32) -> i32 {
CONC_METHOD_CALLS.fetch_add(1, Ordering::SeqCst);
k + 1
}
#[once(in_impl = true)]
fn once_method(&self, k: i32) -> i32 {
ONCE_METHOD_CALLS.fetch_add(1, Ordering::SeqCst);
k
}
}
#[test]
fn in_impl_concurrent_and_once_methods() {
CONC_METHOD_CALLS.store(0, Ordering::SeqCst);
ONCE_METHOD_CALLS.store(0, Ordering::SeqCst);
let s = Svc;
assert_eq!(s.conc_method(5), 6);
assert_eq!(s.conc_method(5), 6);
assert_eq!(CONC_METHOD_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(s.once_method(3), 3);
assert_eq!(s.once_method(9), 3); assert_eq!(ONCE_METHOD_CALLS.load(Ordering::SeqCst), 1);
}
struct Refresher;
static IN_IMPL_FR_CALLS: AtomicUsize = AtomicUsize::new(0);
static IN_IMPL_FR_SOURCE: AtomicUsize = AtomicUsize::new(1);
impl Refresher {
#[cached(
in_impl = true,
key = "i32",
convert = "{ k }",
force_refresh = "{ bypass }"
)]
fn load(&self, k: i32, bypass: bool) -> usize {
IN_IMPL_FR_CALLS.fetch_add(1, Ordering::SeqCst);
let _ = bypass; (k as usize) + IN_IMPL_FR_SOURCE.load(Ordering::SeqCst)
}
}
#[test]
fn force_refresh_composes_with_in_impl() {
IN_IMPL_FR_CALLS.store(0, Ordering::SeqCst);
IN_IMPL_FR_SOURCE.store(1, Ordering::SeqCst);
let r = Refresher;
assert_eq!(r.load(1, false), 2);
assert_eq!(IN_IMPL_FR_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(r.load(1, false), 2);
assert_eq!(IN_IMPL_FR_CALLS.load(Ordering::SeqCst), 1);
IN_IMPL_FR_SOURCE.store(100, Ordering::SeqCst);
assert_eq!(r.load(1, true), 101);
assert_eq!(IN_IMPL_FR_CALLS.load(Ordering::SeqCst), 2);
assert_eq!(r.load(1, false), 101);
assert_eq!(IN_IMPL_FR_CALLS.load(Ordering::SeqCst), 2);
}
struct PubImplStruct;
static PUB_IMPL_CALLS: AtomicUsize = AtomicUsize::new(0);
impl PubImplStruct {
#[cached(in_impl = true)]
pub fn pub_cached_method(&self, x: i32) -> i32 {
PUB_IMPL_CALLS.fetch_add(1, Ordering::SeqCst);
x * 3
}
}
#[test]
fn in_impl_pub_method_caches() {
PUB_IMPL_CALLS.store(0, Ordering::SeqCst);
let s = PubImplStruct;
assert_eq!(s.pub_cached_method(4), 12);
assert_eq!(s.pub_cached_method(4), 12); assert_eq!(
PUB_IMPL_CALLS.load(Ordering::SeqCst),
1,
"second call with the same arg must be a cache hit"
);
assert_eq!(s.pub_cached_method(5), 15); assert_eq!(PUB_IMPL_CALLS.load(Ordering::SeqCst), 2);
}
struct NoCacheSiblingStruct;
static NO_CACHE_SIBLING_CALLS: AtomicUsize = AtomicUsize::new(0);
impl NoCacheSiblingStruct {
#[cached(in_impl = true)]
pub fn cached_method(&self, x: i32) -> i32 {
NO_CACHE_SIBLING_CALLS.fetch_add(1, Ordering::SeqCst);
x * 3
}
}
#[test]
fn in_impl_no_cache_sibling_bypasses_cache() {
let s = NoCacheSiblingStruct;
assert_eq!(s.cached_method(7), 21);
assert_eq!(NO_CACHE_SIBLING_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(s.cached_method(7), 21);
assert_eq!(NO_CACHE_SIBLING_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(s.cached_method_no_cache(7), 21);
assert_eq!(
NO_CACHE_SIBLING_CALLS.load(Ordering::SeqCst),
2,
"_no_cache sibling must bypass the cache and run the body"
);
}
struct ConcImplRefresher;
static CONC_IMPL_FR_CALLS: AtomicUsize = AtomicUsize::new(0);
static CONC_IMPL_FR_SOURCE: AtomicUsize = AtomicUsize::new(1);
impl ConcImplRefresher {
#[concurrent_cached(
in_impl = true,
key = "i32",
convert = "{ k }",
force_refresh = "{ bypass }"
)]
fn conc_impl_load(&self, k: i32, bypass: bool) -> usize {
CONC_IMPL_FR_CALLS.fetch_add(1, Ordering::SeqCst);
let _ = bypass; (k as usize) + CONC_IMPL_FR_SOURCE.load(Ordering::SeqCst)
}
}
#[test]
fn concurrent_in_impl_force_refresh_bypasses_cache() {
CONC_IMPL_FR_CALLS.store(0, Ordering::SeqCst);
CONC_IMPL_FR_SOURCE.store(1, Ordering::SeqCst);
let r = ConcImplRefresher;
assert_eq!(r.conc_impl_load(2, false), 3);
assert_eq!(CONC_IMPL_FR_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(r.conc_impl_load(2, false), 3);
assert_eq!(CONC_IMPL_FR_CALLS.load(Ordering::SeqCst), 1);
CONC_IMPL_FR_SOURCE.store(100, Ordering::SeqCst);
assert_eq!(r.conc_impl_load(2, true), 102);
assert_eq!(CONC_IMPL_FR_CALLS.load(Ordering::SeqCst), 2);
assert_eq!(r.conc_impl_load(2, false), 102);
assert_eq!(CONC_IMPL_FR_CALLS.load(Ordering::SeqCst), 2);
}
#[cfg(feature = "time_stores")]
mod ttl_millis_tests {
use super::*;
use std::thread::sleep;
use std::time::Duration;
static MILLIS_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(ttl_millis = 50)]
fn millis_fn(x: i32) -> i32 {
MILLIS_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn ttl_millis_recomputes_after_expiry() {
MILLIS_CALLS.store(0, Ordering::SeqCst);
assert_eq!(millis_fn(7), 7);
assert_eq!(millis_fn(7), 7);
assert_eq!(
MILLIS_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(70));
assert_eq!(millis_fn(7), 7);
assert_eq!(
MILLIS_CALLS.load(Ordering::SeqCst),
2,
"after ttl_millis expiry: recompute"
);
}
static CONC_MILLIS_CALLS: AtomicUsize = AtomicUsize::new(0);
#[concurrent_cached(ttl_millis = 50)]
fn conc_millis_fn(x: i32) -> i32 {
CONC_MILLIS_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn concurrent_ttl_millis_recomputes_after_expiry() {
CONC_MILLIS_CALLS.store(0, Ordering::SeqCst);
assert_eq!(conc_millis_fn(7), 7);
assert_eq!(conc_millis_fn(7), 7);
assert_eq!(
CONC_MILLIS_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(70));
assert_eq!(conc_millis_fn(7), 7);
assert_eq!(
CONC_MILLIS_CALLS.load(Ordering::SeqCst),
2,
"after ttl_millis expiry: recompute"
);
}
static ONCE_MILLIS_CALLS: AtomicUsize = AtomicUsize::new(0);
#[once(ttl_millis = 50)]
fn once_millis_fn() -> usize {
ONCE_MILLIS_CALLS.fetch_add(1, Ordering::SeqCst) + 1
}
#[test]
fn once_ttl_millis_recomputes_after_expiry() {
ONCE_MILLIS_CALLS.store(0, Ordering::SeqCst);
assert_eq!(once_millis_fn(), 1);
assert_eq!(once_millis_fn(), 1);
assert_eq!(
ONCE_MILLIS_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(70));
assert_eq!(once_millis_fn(), 2);
assert_eq!(
ONCE_MILLIS_CALLS.load(Ordering::SeqCst),
2,
"after ttl_millis expiry: recompute"
);
}
struct TtlImplStruct;
static TTL_IMPL_CALLS: AtomicUsize = AtomicUsize::new(0);
impl TtlImplStruct {
#[cached(in_impl = true, ttl_millis = 50)]
fn ttl_method(&self, x: i32) -> i32 {
TTL_IMPL_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
}
#[test]
fn in_impl_ttl_millis_caches_and_recomputes() {
TTL_IMPL_CALLS.store(0, Ordering::SeqCst);
let s = TtlImplStruct;
assert_eq!(s.ttl_method(9), 9);
assert_eq!(s.ttl_method(9), 9);
assert_eq!(
TTL_IMPL_CALLS.load(Ordering::SeqCst),
1,
"within TTL: in_impl method must serve from cache"
);
sleep(Duration::from_millis(70));
assert_eq!(s.ttl_method(9), 9);
assert_eq!(
TTL_IMPL_CALLS.load(Ordering::SeqCst),
2,
"after ttl_millis expiry: in_impl method must recompute"
);
}
static ONCE_TTL_FR_CALLS: AtomicUsize = AtomicUsize::new(0);
static ONCE_TTL_FR_SOURCE: AtomicUsize = AtomicUsize::new(1);
#[once(ttl_millis = 600_000, force_refresh = "{ bypass }")]
fn once_ttl_fr(bypass: bool) -> usize {
let _ = bypass; ONCE_TTL_FR_CALLS.fetch_add(1, Ordering::SeqCst);
ONCE_TTL_FR_SOURCE.load(Ordering::SeqCst)
}
#[test]
fn once_ttl_millis_force_refresh_recomputes_before_expiry() {
ONCE_TTL_FR_CALLS.store(0, Ordering::SeqCst);
ONCE_TTL_FR_SOURCE.store(10, Ordering::SeqCst);
assert_eq!(once_ttl_fr(false), 10);
assert_eq!(ONCE_TTL_FR_CALLS.load(Ordering::SeqCst), 1);
ONCE_TTL_FR_SOURCE.store(99, Ordering::SeqCst);
assert_eq!(once_ttl_fr(false), 10, "within TTL: cached value returned");
assert_eq!(ONCE_TTL_FR_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(once_ttl_fr(true), 99, "force_refresh recomputed new source");
assert_eq!(ONCE_TTL_FR_CALLS.load(Ordering::SeqCst), 2);
assert_eq!(once_ttl_fr(false), 99, "later call sees overwritten value");
assert_eq!(ONCE_TTL_FR_CALLS.load(Ordering::SeqCst), 2);
}
static CACHED_TTL_FR_CALLS: AtomicUsize = AtomicUsize::new(0);
static CACHED_TTL_FR_SOURCE: AtomicUsize = AtomicUsize::new(1);
#[cached(
key = "i32",
convert = "{ x }",
ttl_millis = 600_000,
force_refresh = "{ bypass }"
)]
fn cached_ttl_fr(x: i32, bypass: bool) -> usize {
let _ = bypass; CACHED_TTL_FR_CALLS.fetch_add(1, Ordering::SeqCst);
x as usize + CACHED_TTL_FR_SOURCE.load(Ordering::SeqCst)
}
#[test]
fn cached_ttl_millis_force_refresh_recomputes_before_expiry() {
CACHED_TTL_FR_CALLS.store(0, Ordering::SeqCst);
CACHED_TTL_FR_SOURCE.store(1, Ordering::SeqCst);
assert_eq!(cached_ttl_fr(3, false), 4);
assert_eq!(CACHED_TTL_FR_CALLS.load(Ordering::SeqCst), 1);
CACHED_TTL_FR_SOURCE.store(100, Ordering::SeqCst);
assert_eq!(
cached_ttl_fr(3, false),
4,
"within TTL: cached value returned"
);
assert_eq!(CACHED_TTL_FR_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(
cached_ttl_fr(3, true),
103,
"force_refresh recomputed new source"
);
assert_eq!(CACHED_TTL_FR_CALLS.load(Ordering::SeqCst), 2);
assert_eq!(
cached_ttl_fr(3, false),
103,
"later call sees overwritten value"
);
assert_eq!(CACHED_TTL_FR_CALLS.load(Ordering::SeqCst), 2);
}
static LRU_MILLIS_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(max_size = 10, ttl_millis = 50)]
fn lru_millis_fn(x: i32) -> i32 {
LRU_MILLIS_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn lru_ttl_millis_recomputes_after_expiry() {
LRU_MILLIS_CALLS.store(0, Ordering::SeqCst);
assert_eq!(lru_millis_fn(7), 7);
assert_eq!(lru_millis_fn(7), 7);
assert_eq!(
LRU_MILLIS_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(70));
assert_eq!(lru_millis_fn(7), 7);
assert_eq!(
LRU_MILLIS_CALLS.load(Ordering::SeqCst),
2,
"after ttl_millis expiry: recompute"
);
}
struct ConcTtlImplStruct;
static CONC_TTL_IMPL_CALLS: AtomicUsize = AtomicUsize::new(0);
impl ConcTtlImplStruct {
#[concurrent_cached(in_impl = true, ttl_millis = 50)]
fn ttl_method(&self, x: i32) -> i32 {
CONC_TTL_IMPL_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
}
#[test]
fn concurrent_in_impl_ttl_millis_caches_and_recomputes() {
CONC_TTL_IMPL_CALLS.store(0, Ordering::SeqCst);
let s = ConcTtlImplStruct;
assert_eq!(s.ttl_method(9), 9);
assert_eq!(s.ttl_method(9), 9);
assert_eq!(
CONC_TTL_IMPL_CALLS.load(Ordering::SeqCst),
1,
"within TTL: in_impl method must serve from cache"
);
sleep(Duration::from_millis(70));
assert_eq!(s.ttl_method(9), 9);
assert_eq!(
CONC_TTL_IMPL_CALLS.load(Ordering::SeqCst),
2,
"after ttl_millis expiry: in_impl method must recompute"
);
}
struct OnceTtlImplStruct;
static ONCE_TTL_IMPL_CALLS: AtomicUsize = AtomicUsize::new(0);
impl OnceTtlImplStruct {
#[once(in_impl = true, ttl_millis = 50)]
fn ttl_method(&self) -> usize {
ONCE_TTL_IMPL_CALLS.fetch_add(1, Ordering::SeqCst) + 1
}
}
#[test]
fn once_in_impl_ttl_millis_caches_and_recomputes() {
ONCE_TTL_IMPL_CALLS.store(0, Ordering::SeqCst);
let s = OnceTtlImplStruct;
assert_eq!(s.ttl_method(), 1);
assert_eq!(s.ttl_method(), 1);
assert_eq!(
ONCE_TTL_IMPL_CALLS.load(Ordering::SeqCst),
1,
"within TTL: in_impl once method must serve the cached value"
);
sleep(Duration::from_millis(70));
assert_eq!(s.ttl_method(), 2);
assert_eq!(
ONCE_TTL_IMPL_CALLS.load(Ordering::SeqCst),
2,
"after ttl_millis expiry: in_impl once method must recompute"
);
}
static CONC_TTL_FR_CALLS: AtomicUsize = AtomicUsize::new(0);
static CONC_TTL_FR_SOURCE: AtomicUsize = AtomicUsize::new(1);
#[concurrent_cached(
key = "i32",
convert = "{ x }",
ttl_millis = 600_000,
force_refresh = "{ bypass }"
)]
fn conc_ttl_fr(x: i32, bypass: bool) -> usize {
let _ = bypass; CONC_TTL_FR_CALLS.fetch_add(1, Ordering::SeqCst);
x as usize + CONC_TTL_FR_SOURCE.load(Ordering::SeqCst)
}
#[test]
fn concurrent_ttl_millis_force_refresh_recomputes_before_expiry() {
CONC_TTL_FR_CALLS.store(0, Ordering::SeqCst);
CONC_TTL_FR_SOURCE.store(1, Ordering::SeqCst);
assert_eq!(conc_ttl_fr(3, false), 4);
assert_eq!(CONC_TTL_FR_CALLS.load(Ordering::SeqCst), 1);
CONC_TTL_FR_SOURCE.store(100, Ordering::SeqCst);
assert_eq!(
conc_ttl_fr(3, false),
4,
"within TTL: cached value returned"
);
assert_eq!(CONC_TTL_FR_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(
conc_ttl_fr(3, true),
103,
"force_refresh recomputed new source"
);
assert_eq!(CONC_TTL_FR_CALLS.load(Ordering::SeqCst), 2);
assert_eq!(
conc_ttl_fr(3, false),
103,
"later call sees overwritten value"
);
assert_eq!(CONC_TTL_FR_CALLS.load(Ordering::SeqCst), 2);
}
static REFRESH_CONC_CALLS: AtomicUsize = AtomicUsize::new(0);
#[concurrent_cached(ttl_millis = 600_000, refresh = true)]
fn conc_refresh_fn(x: i32) -> i32 {
REFRESH_CONC_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn concurrent_cached_refresh_bool_compiles_and_caches() {
REFRESH_CONC_CALLS.store(0, Ordering::SeqCst);
assert_eq!(conc_refresh_fn(7), 7);
assert_eq!(REFRESH_CONC_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(conc_refresh_fn(7), 7);
assert_eq!(
REFRESH_CONC_CALLS.load(Ordering::SeqCst),
1,
"refresh = true (bool) still caches: second call must be a hit"
);
}
}
#[cfg(feature = "time_stores")]
mod ttl_spelling_tests {
use super::*;
use std::thread::sleep;
use std::time::Duration;
static TTL_EXPR_CACHED_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(ttl = "core::time::Duration::from_millis(50)")]
fn ttl_expr_cached(x: i32) -> i32 {
TTL_EXPR_CACHED_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn ttl_expr_cached_recomputes_after_expiry() {
TTL_EXPR_CACHED_CALLS.store(0, Ordering::SeqCst);
assert_eq!(ttl_expr_cached(7), 7);
assert_eq!(ttl_expr_cached(7), 7);
assert_eq!(
TTL_EXPR_CACHED_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(70));
assert_eq!(ttl_expr_cached(7), 7);
assert_eq!(
TTL_EXPR_CACHED_CALLS.load(Ordering::SeqCst),
2,
"after `ttl` Duration expiry: recompute"
);
}
static TTL_EXPR_ONCE_CALLS: AtomicUsize = AtomicUsize::new(0);
#[once(ttl = "core::time::Duration::from_millis(50)")]
fn ttl_expr_once() -> usize {
TTL_EXPR_ONCE_CALLS.fetch_add(1, Ordering::SeqCst) + 1
}
#[test]
fn ttl_expr_once_recomputes_after_expiry() {
TTL_EXPR_ONCE_CALLS.store(0, Ordering::SeqCst);
assert_eq!(ttl_expr_once(), 1);
assert_eq!(ttl_expr_once(), 1);
assert_eq!(
TTL_EXPR_ONCE_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(70));
assert_eq!(ttl_expr_once(), 2);
assert_eq!(
TTL_EXPR_ONCE_CALLS.load(Ordering::SeqCst),
2,
"after `ttl` Duration expiry: recompute"
);
}
static TTL_EXPR_CONC_CALLS: AtomicUsize = AtomicUsize::new(0);
#[concurrent_cached(ttl = "core::time::Duration::from_millis(50)")]
fn ttl_expr_conc(x: i32) -> i32 {
TTL_EXPR_CONC_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn ttl_expr_concurrent_recomputes_after_expiry() {
TTL_EXPR_CONC_CALLS.store(0, Ordering::SeqCst);
assert_eq!(ttl_expr_conc(7), 7);
assert_eq!(ttl_expr_conc(7), 7);
assert_eq!(
TTL_EXPR_CONC_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(70));
assert_eq!(ttl_expr_conc(7), 7);
assert_eq!(
TTL_EXPR_CONC_CALLS.load(Ordering::SeqCst),
2,
"after `ttl` Duration expiry: recompute"
);
}
static TTL_SECS_CACHED_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(ttl_secs = 1)]
fn ttl_secs_cached(x: i32) -> i32 {
TTL_SECS_CACHED_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn ttl_secs_cached_recomputes_after_expiry() {
TTL_SECS_CACHED_CALLS.store(0, Ordering::SeqCst);
assert_eq!(ttl_secs_cached(7), 7);
assert_eq!(ttl_secs_cached(7), 7);
assert_eq!(
TTL_SECS_CACHED_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(1_100));
assert_eq!(ttl_secs_cached(7), 7);
assert_eq!(
TTL_SECS_CACHED_CALLS.load(Ordering::SeqCst),
2,
"after `ttl_secs` expiry: recompute"
);
}
static TTL_SECS_ONCE_CALLS: AtomicUsize = AtomicUsize::new(0);
#[once(ttl_secs = 1)]
fn ttl_secs_once() -> usize {
TTL_SECS_ONCE_CALLS.fetch_add(1, Ordering::SeqCst) + 1
}
#[test]
fn ttl_secs_once_recomputes_after_expiry() {
TTL_SECS_ONCE_CALLS.store(0, Ordering::SeqCst);
assert_eq!(ttl_secs_once(), 1);
assert_eq!(ttl_secs_once(), 1);
assert_eq!(
TTL_SECS_ONCE_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(1_100));
assert_eq!(ttl_secs_once(), 2);
assert_eq!(
TTL_SECS_ONCE_CALLS.load(Ordering::SeqCst),
2,
"after `ttl_secs` expiry: recompute"
);
}
static TTL_SECS_CONC_CALLS: AtomicUsize = AtomicUsize::new(0);
#[concurrent_cached(ttl_secs = 1)]
fn ttl_secs_conc(x: i32) -> i32 {
TTL_SECS_CONC_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn ttl_secs_concurrent_recomputes_after_expiry() {
TTL_SECS_CONC_CALLS.store(0, Ordering::SeqCst);
assert_eq!(ttl_secs_conc(7), 7);
assert_eq!(ttl_secs_conc(7), 7);
assert_eq!(
TTL_SECS_CONC_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(1_100));
assert_eq!(ttl_secs_conc(7), 7);
assert_eq!(
TTL_SECS_CONC_CALLS.load(Ordering::SeqCst),
2,
"after `ttl_secs` expiry: recompute"
);
}
static TTL_MILLIS_CACHED_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(ttl_millis = 50)]
fn ttl_millis_cached(x: i32) -> i32 {
TTL_MILLIS_CACHED_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn ttl_millis_cached_recomputes_after_expiry() {
TTL_MILLIS_CACHED_CALLS.store(0, Ordering::SeqCst);
assert_eq!(ttl_millis_cached(7), 7);
assert_eq!(ttl_millis_cached(7), 7);
assert_eq!(
TTL_MILLIS_CACHED_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(70));
assert_eq!(ttl_millis_cached(7), 7);
assert_eq!(
TTL_MILLIS_CACHED_CALLS.load(Ordering::SeqCst),
2,
"after `ttl_millis` expiry: recompute"
);
}
static TTL_MILLIS_ONCE_CALLS: AtomicUsize = AtomicUsize::new(0);
#[once(ttl_millis = 50)]
fn ttl_millis_once() -> usize {
TTL_MILLIS_ONCE_CALLS.fetch_add(1, Ordering::SeqCst) + 1
}
#[test]
fn ttl_millis_once_recomputes_after_expiry() {
TTL_MILLIS_ONCE_CALLS.store(0, Ordering::SeqCst);
assert_eq!(ttl_millis_once(), 1);
assert_eq!(ttl_millis_once(), 1);
assert_eq!(
TTL_MILLIS_ONCE_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(70));
assert_eq!(ttl_millis_once(), 2);
assert_eq!(
TTL_MILLIS_ONCE_CALLS.load(Ordering::SeqCst),
2,
"after `ttl_millis` expiry: recompute"
);
}
static TTL_MILLIS_CONC_CALLS: AtomicUsize = AtomicUsize::new(0);
#[concurrent_cached(ttl_millis = 50)]
fn ttl_millis_conc(x: i32) -> i32 {
TTL_MILLIS_CONC_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn ttl_millis_concurrent_recomputes_after_expiry() {
TTL_MILLIS_CONC_CALLS.store(0, Ordering::SeqCst);
assert_eq!(ttl_millis_conc(7), 7);
assert_eq!(ttl_millis_conc(7), 7);
assert_eq!(
TTL_MILLIS_CONC_CALLS.load(Ordering::SeqCst),
1,
"within TTL: cache hit"
);
sleep(Duration::from_millis(70));
assert_eq!(ttl_millis_conc(7), 7);
assert_eq!(
TTL_MILLIS_CONC_CALLS.load(Ordering::SeqCst),
2,
"after `ttl_millis` expiry: recompute"
);
}
}
static ONCE_SW_FR_PRED_COUNT: AtomicUsize = AtomicUsize::new(0);
static ONCE_SW_FR_BODY_COUNT: AtomicUsize = AtomicUsize::new(0);
#[once(
sync_writes,
force_refresh = "{ ONCE_SW_FR_PRED_COUNT.fetch_add(1, Ordering::SeqCst); false }"
)]
fn once_sync_writes_fr(x: usize) -> usize {
ONCE_SW_FR_BODY_COUNT.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn once_sync_writes_force_refresh_predicate_eval_count() {
ONCE_SW_FR_PRED_COUNT.store(0, Ordering::SeqCst);
ONCE_SW_FR_BODY_COUNT.store(0, Ordering::SeqCst);
let _ = once_sync_writes_fr(42);
assert_eq!(
ONCE_SW_FR_BODY_COUNT.load(Ordering::SeqCst),
1,
"body must run exactly once on a cache miss"
);
assert_eq!(
ONCE_SW_FR_PRED_COUNT.load(Ordering::SeqCst),
1,
"force_refresh predicate must be evaluated EXACTLY ONCE per call, not twice (#FIX-B)"
);
let _ = once_sync_writes_fr(42);
assert_eq!(
ONCE_SW_FR_BODY_COUNT.load(Ordering::SeqCst),
1,
"body must not run again on a cache hit"
);
assert_eq!(
ONCE_SW_FR_PRED_COUNT.load(Ordering::SeqCst),
2,
"predicate evaluated once per call (2 calls total)"
);
}
static OPT_MUT_REF_BODY_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cached]
fn opt_mut_ref_cached(s: Option<&mut String>) -> usize {
OPT_MUT_REF_BODY_COUNT.fetch_add(1, Ordering::SeqCst);
s.as_deref().map_or(0, |v| v.len())
}
#[test]
fn opt_mut_ref_default_key_compiles_and_caches() {
OPT_MUT_REF_BODY_COUNT.store(0, Ordering::SeqCst);
let mut a = String::from("hello");
let mut b = String::from("hello");
let r1 = opt_mut_ref_cached(Some(&mut a));
let r2 = opt_mut_ref_cached(Some(&mut b));
assert_eq!(r1, 5);
assert_eq!(r2, 5);
assert_eq!(
OPT_MUT_REF_BODY_COUNT.load(Ordering::SeqCst),
1,
"Option<&mut String> with equal keys: body must run exactly once (cache hit on second call)"
);
let mut c = String::from("world!");
let r3 = opt_mut_ref_cached(Some(&mut c));
assert_eq!(r3, 6);
assert_eq!(OPT_MUT_REF_BODY_COUNT.load(Ordering::SeqCst), 2);
let r4 = opt_mut_ref_cached(None);
let r5 = opt_mut_ref_cached(None);
assert_eq!(r4, 0);
assert_eq!(r5, 0);
assert_eq!(
OPT_MUT_REF_BODY_COUNT.load(Ordering::SeqCst),
3,
"None key: body runs once, second call is a cache hit"
);
}
#[cfg(feature = "async")]
mod async_in_impl_tests {
use super::*;
struct AsyncSvc;
static ASYNC_ONCE_CALLS: AtomicUsize = AtomicUsize::new(0);
impl AsyncSvc {
#[once(in_impl = true)]
async fn load(&self, x: i32) -> i32 {
ASYNC_ONCE_CALLS.fetch_add(1, Ordering::SeqCst);
x * 2
}
}
#[tokio::test]
async fn async_in_impl_once_caches_across_awaits() {
ASYNC_ONCE_CALLS.store(0, Ordering::SeqCst);
let s = AsyncSvc;
assert_eq!(s.load(5).await, 10);
assert_eq!(
s.load(7).await,
10,
"once: single value shared across awaits"
);
assert_eq!(
ASYNC_ONCE_CALLS.load(Ordering::SeqCst),
1,
"async in_impl once: body runs exactly once"
);
}
struct AsyncCachedSvc;
static ASYNC_CACHED_CALLS: AtomicUsize = AtomicUsize::new(0);
impl AsyncCachedSvc {
#[cached(in_impl = true)]
async fn compute(&self, x: i32) -> i32 {
ASYNC_CACHED_CALLS.fetch_add(1, Ordering::SeqCst);
x * 3
}
}
#[tokio::test]
async fn async_in_impl_cached_caches_per_key() {
ASYNC_CACHED_CALLS.store(0, Ordering::SeqCst);
let s = AsyncCachedSvc;
assert_eq!(s.compute(4).await, 12);
assert_eq!(ASYNC_CACHED_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(s.compute(4).await, 12);
assert_eq!(
ASYNC_CACHED_CALLS.load(Ordering::SeqCst),
1,
"async in_impl cached: second await with same arg must be a cache hit"
);
assert_eq!(s.compute(5).await, 15);
assert_eq!(ASYNC_CACHED_CALLS.load(Ordering::SeqCst), 2);
}
struct AsyncConcSvc;
static ASYNC_CONC_CALLS: AtomicUsize = AtomicUsize::new(0);
impl AsyncConcSvc {
#[concurrent_cached(in_impl = true)]
async fn fetch(&self, x: i32) -> i32 {
ASYNC_CONC_CALLS.fetch_add(1, Ordering::SeqCst);
x + 10
}
}
#[tokio::test]
async fn async_in_impl_concurrent_caches_per_key() {
ASYNC_CONC_CALLS.store(0, Ordering::SeqCst);
let s = AsyncConcSvc;
assert_eq!(s.fetch(7).await, 17);
assert_eq!(ASYNC_CONC_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(s.fetch(7).await, 17);
assert_eq!(
ASYNC_CONC_CALLS.load(Ordering::SeqCst),
1,
"async in_impl concurrent_cached: second await with same arg must be a cache hit"
);
assert_eq!(s.fetch(3).await, 13);
assert_eq!(ASYNC_CONC_CALLS.load(Ordering::SeqCst), 2);
}
}
mod unbound_default_tests {
use super::*;
static UNBOUND_REPEAT_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached]
fn unbound_repeat(x: u32) -> u32 {
UNBOUND_REPEAT_CALLS.fetch_add(1, Ordering::SeqCst);
x * 2
}
#[test]
fn plain_cached_caches_repeated_same_arg() {
assert_eq!(unbound_repeat(21), 42);
assert_eq!(UNBOUND_REPEAT_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(unbound_repeat(21), 42);
assert_eq!(
UNBOUND_REPEAT_CALLS.load(Ordering::SeqCst),
1,
"plain #[cached] (no unbound attr) must cache repeated same-arg calls"
);
}
static UNBOUND_FILL_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached]
fn unbound_fill(x: u32) -> u32 {
UNBOUND_FILL_CALLS.fetch_add(1, Ordering::SeqCst);
x * 2
}
#[test]
fn plain_cached_is_unbounded_no_eviction() {
for i in 100..1100u32 {
assert_eq!(unbound_fill(i), i * 2);
}
let after_fill = UNBOUND_FILL_CALLS.load(Ordering::SeqCst);
assert_eq!(after_fill, 1000, "1000 distinct keys each computed once");
assert_eq!(unbound_fill(100), 200);
assert_eq!(
UNBOUND_FILL_CALLS.load(Ordering::SeqCst),
after_fill,
"default #[cached] is unbounded: the earliest key is never evicted"
);
}
}
mod refresh_false_no_conflict_tests {
use super::*;
#[derive(Clone)]
struct NeverExpires(u32);
impl cached::Expires for NeverExpires {
fn is_expired(&self) -> bool {
false
}
}
static REFRESH_FALSE_EXPIRES_CALLS: AtomicUsize = AtomicUsize::new(0);
#[concurrent_cached(expires = true, refresh = false)]
fn refresh_false_expires(x: u32) -> NeverExpires {
REFRESH_FALSE_EXPIRES_CALLS.fetch_add(1, Ordering::SeqCst);
NeverExpires(x)
}
#[test]
fn refresh_false_does_not_conflict_with_expires() {
REFRESH_FALSE_EXPIRES_CALLS.store(0, Ordering::SeqCst);
assert_eq!(refresh_false_expires(9).0, 9);
assert_eq!(REFRESH_FALSE_EXPIRES_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(refresh_false_expires(9).0, 9);
assert_eq!(
REFRESH_FALSE_EXPIRES_CALLS.load(Ordering::SeqCst),
1,
"refresh = false + expires = true must compile and cache"
);
}
}
static VALID_NAME_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(name = "MY_CACHE")]
fn valid_name_caches(x: u32) -> u32 {
VALID_NAME_CALLS.fetch_add(1, Ordering::SeqCst);
x + 1
}
#[test]
fn valid_name_compiles_and_caches() {
VALID_NAME_CALLS.store(0, Ordering::SeqCst);
assert_eq!(valid_name_caches(5), 6);
assert_eq!(VALID_NAME_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(valid_name_caches(5), 6);
assert_eq!(
VALID_NAME_CALLS.load(Ordering::SeqCst),
1,
"a valid `name` must produce a working memoizing cache"
);
assert_eq!(valid_name_caches(10), 11);
assert_eq!(VALID_NAME_CALLS.load(Ordering::SeqCst), 2);
use cached::Cached;
assert!(MY_CACHE.read().cache_size() >= 2);
}
static ONCE_SW_DEFAULT_CALLS: AtomicUsize = AtomicUsize::new(0);
#[once(sync_writes = "default")]
fn once_sync_writes_default(x: usize) -> usize {
ONCE_SW_DEFAULT_CALLS.fetch_add(1, Ordering::SeqCst);
x * 2
}
#[test]
fn once_sync_writes_default_compiles_and_caches() {
ONCE_SW_DEFAULT_CALLS.store(0, Ordering::SeqCst);
assert_eq!(once_sync_writes_default(21), 42);
assert_eq!(ONCE_SW_DEFAULT_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(once_sync_writes_default(100), 42);
assert_eq!(
ONCE_SW_DEFAULT_CALLS.load(Ordering::SeqCst),
1,
"`sync_writes = \"default\"` on `#[once]` must still compile and cache the one value"
);
}
static ONCE_SW_TRUE_CALLS: AtomicUsize = AtomicUsize::new(0);
#[once(sync_writes = true)]
fn once_sync_writes_true(x: usize) -> usize {
ONCE_SW_TRUE_CALLS.fetch_add(1, Ordering::SeqCst);
x + 7
}
#[test]
fn once_sync_writes_true_compiles_and_caches() {
ONCE_SW_TRUE_CALLS.store(0, Ordering::SeqCst);
assert_eq!(once_sync_writes_true(1), 8);
assert_eq!(ONCE_SW_TRUE_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(once_sync_writes_true(999), 8);
assert_eq!(
ONCE_SW_TRUE_CALLS.load(Ordering::SeqCst),
1,
"`sync_writes = true` on `#[once]` must still compile and cache"
);
}
static CACHED_DEFAULT_DISABLED_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(key = "u32", convert = { k })]
fn cached_default_disabled(k: u32) -> u32 {
CACHED_DEFAULT_DISABLED_CALLS.fetch_add(1, Ordering::SeqCst);
k * 2
}
#[test]
fn test_cached_default_is_disabled_not_by_key() {
use cached::Cached;
CACHED_DEFAULT_DISABLED_CALLS.store(0, Ordering::SeqCst);
assert_eq!(cached_default_disabled(1), 2);
let hits_before = CACHED_DEFAULT_DISABLED.read().cache_hits();
assert_eq!(cached_default_disabled(1), 2); let hits_after = CACHED_DEFAULT_DISABLED.read().cache_hits();
assert!(
hits_after > hits_before,
"bare #[cached] must still cache by default"
);
assert_eq!(CACHED_DEFAULT_DISABLED_CALLS.load(Ordering::SeqCst), 1);
}
#[cached(sync_writes = "by_key", sync_writes_buckets = 8)]
fn by_key_inspectable(x: u32) -> u32 {
x * 2
}
#[test]
fn by_key_named_static_inspectable_via_read_and_write() {
use cached::{Cached, CachedRead};
assert_eq!(by_key_inspectable(2), 4);
assert_eq!(by_key_inspectable(3), 6);
{
let guard = BY_KEY_INSPECTABLE.read();
assert_eq!(CachedRead::cache_get_read(&*guard, &2), Some(&4));
assert_eq!(CachedRead::cache_get_read(&*guard, &3), Some(&6));
}
{
let mut guard = BY_KEY_INSPECTABLE.write();
assert_eq!(guard.cache_get(&2), Some(&4));
guard.cache_clear();
}
assert_eq!(
CachedRead::cache_get_read(&*BY_KEY_INSPECTABLE.read(), &2),
None
);
assert_eq!(by_key_inspectable(2), 4);
assert!(BY_KEY_INSPECTABLE.write().cache_get(&2).is_some());
}
static CACHED_SW_FALSE_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(key = "u32", convert = { k }, sync_writes = false)]
fn cached_sw_false(k: u32) -> u32 {
CACHED_SW_FALSE_CALLS.fetch_add(1, Ordering::SeqCst);
k * 3
}
#[test]
fn test_cached_sync_writes_false_double_compute() {
CACHED_SW_FALSE_CALLS.store(0, Ordering::SeqCst);
assert_eq!(cached_sw_false(5), 15);
use cached::Cached;
let hits_before = CACHED_SW_FALSE.read().cache_hits();
assert_eq!(cached_sw_false(5), 15); let hits_after = CACHED_SW_FALSE.read().cache_hits();
assert!(
hits_after > hits_before,
"sync_writes = false: cache should hit on repeated call"
);
}
#[cfg(feature = "time_stores")]
static CACHED_RF_NO_SW_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cfg(feature = "time_stores")]
#[cached(ttl_secs = 3600, result_fallback = true, key = "u32", convert = { k })]
fn cached_result_fallback_no_sync_writes(k: u32) -> Result<u32, String> {
CACHED_RF_NO_SW_CALLS.fetch_add(1, Ordering::SeqCst);
Ok(k)
}
#[cfg(feature = "time_stores")]
#[test]
fn test_cached_result_fallback_no_explicit_sync_writes_compiles() {
CACHED_RF_NO_SW_CALLS.store(0, Ordering::SeqCst);
let v = cached_result_fallback_no_sync_writes(7).unwrap();
assert_eq!(v, 7);
let cached_v = cached_result_fallback_no_sync_writes(7).unwrap();
assert_eq!(cached_v, 7);
assert_eq!(CACHED_RF_NO_SW_CALLS.load(Ordering::SeqCst), 1);
}
static UNQUOTED_CONVERT_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(key = "String", convert = { format!("{a}") })]
fn unquoted_convert(a: u32) -> u32 {
UNQUOTED_CONVERT_CALLS.fetch_add(1, Ordering::SeqCst);
a
}
#[test]
fn test_cached_unquoted_convert_compiles_and_caches() {
UNQUOTED_CONVERT_CALLS.store(0, Ordering::SeqCst);
assert_eq!(unquoted_convert(3), 3);
assert_eq!(unquoted_convert(3), 3); assert_eq!(UNQUOTED_CONVERT_CALLS.load(Ordering::SeqCst), 1);
}
#[cached(ty = "cached::UnboundCache<u32, u32>", create = cached::UnboundCache::new())]
fn unquoted_create_bare(x: u32) -> u32 {
x + 1
}
#[cached(ty = "cached::UnboundCache<u32, u32>", create = { cached::UnboundCache::new() })]
fn unquoted_create_block(x: u32) -> u32 {
x + 1
}
#[test]
fn test_cached_unquoted_create_forms_compile_and_cache() {
assert_eq!(unquoted_create_bare(1), 2);
assert_eq!(unquoted_create_bare(1), 2); assert_eq!(unquoted_create_block(1), 2);
assert_eq!(unquoted_create_block(1), 2); }
static QUOTED_CONVERT_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(key = "u32", convert = "{ n + 1 }")]
fn quoted_convert(n: u32) -> u32 {
QUOTED_CONVERT_CALLS.fetch_add(1, Ordering::SeqCst);
n
}
#[test]
fn test_cached_legacy_quoted_convert_compiles_and_caches() {
QUOTED_CONVERT_CALLS.store(0, Ordering::SeqCst);
assert_eq!(quoted_convert(10), 10);
assert_eq!(quoted_convert(10), 10); assert_eq!(QUOTED_CONVERT_CALLS.load(Ordering::SeqCst), 1);
}
static UNQUOTED_FR_CALLS: AtomicUsize = AtomicUsize::new(0);
static UNQUOTED_FR_SRC: AtomicUsize = AtomicUsize::new(42);
#[cached(key = "u32", convert = { k % 100 }, force_refresh = { k == 0 })]
fn unquoted_force_refresh(k: u32) -> u32 {
UNQUOTED_FR_CALLS.fetch_add(1, Ordering::SeqCst);
UNQUOTED_FR_SRC.load(Ordering::SeqCst) as u32 + (k % 100)
}
#[test]
fn test_cached_unquoted_force_refresh_compiles_and_works() {
UNQUOTED_FR_CALLS.store(0, Ordering::SeqCst);
UNQUOTED_FR_SRC.store(10, Ordering::SeqCst);
assert_eq!(unquoted_force_refresh(1), 11);
assert_eq!(unquoted_force_refresh(1), 11); assert_eq!(UNQUOTED_FR_CALLS.load(Ordering::SeqCst), 1);
UNQUOTED_FR_SRC.store(99, Ordering::SeqCst);
assert_eq!(unquoted_force_refresh(0), 99);
assert_eq!(UNQUOTED_FR_CALLS.load(Ordering::SeqCst), 2);
}
#[cfg(all(feature = "redb_store", feature = "proc_macro"))]
mod disk_no_map_error_tests {
use cached::macros::concurrent_cached;
use std::sync::atomic::{AtomicUsize, Ordering};
static DISK_NO_MAP_ERR_CALLS: AtomicUsize = AtomicUsize::new(0);
#[concurrent_cached(disk = true, ttl_secs = 60)]
fn disk_fn_no_map_error(n: u32) -> Result<u32, Box<dyn std::error::Error + Send + Sync>> {
DISK_NO_MAP_ERR_CALLS.fetch_add(1, Ordering::SeqCst);
Ok(n * 2)
}
#[test]
fn test_disk_concurrent_without_map_error_compiles_and_caches() {
assert_eq!(
disk_fn_no_map_error(3).unwrap(),
6,
"disk_fn_no_map_error(3) must return Ok(6)"
);
assert_eq!(
disk_fn_no_map_error(3).unwrap(),
6,
"disk_fn_no_map_error(3) repeated call must return Ok(6)"
);
}
}
mod companions_vis_tests {
use cached::macros::cached;
#[cached(key = "u32", convert = { n }, companions_vis = "pub(crate)")]
pub fn companions_vis_fn(n: u32) -> u32 {
n * 7
}
#[test]
fn test_companions_vis_pub_crate_produces_pub_crate_companions() {
let direct = companions_vis_fn_no_cache(2);
assert_eq!(
direct, 14,
"companions_vis: no_cache companion returned wrong value"
);
}
#[cached(key = "u32", convert = { n })]
pub fn default_companions_vis_fn(n: u32) -> u32 {
n + 1
}
#[test]
fn test_companions_vis_default_inherits_fn_visibility() {
let direct = default_companions_vis_fn_no_cache(5);
assert_eq!(
direct, 6,
"default companions_vis: no_cache companion returned wrong value"
);
}
}
mod companions_vis_once_tests {
use cached::macros::once;
#[once(companions_vis = "pub(crate)")]
pub fn companions_vis_once_fn() -> u32 {
21
}
#[test]
fn test_companions_vis_once_pub_crate_produces_pub_crate_prime_cache() {
let val = companions_vis_once_fn_prime_cache();
assert_eq!(
val, 21,
"companions_vis on #[once]: prime_cache companion returned wrong value"
);
}
#[once]
pub fn default_companions_vis_once_fn() -> u32 {
22
}
#[test]
fn test_companions_vis_once_default_inherits_fn_visibility() {
let val = default_companions_vis_once_fn_prime_cache();
assert_eq!(
val, 22,
"default companions_vis on #[once]: prime_cache companion returned wrong value"
);
}
}
mod companions_vis_concurrent_tests {
use cached::macros::concurrent_cached;
#[concurrent_cached(key = "u32", convert = { n }, companions_vis = "pub(crate)")]
pub fn companions_vis_concurrent_fn(n: u32) -> u32 {
n * 11
}
#[test]
fn test_companions_vis_concurrent_pub_crate_produces_pub_crate_prime_cache() {
let val = companions_vis_concurrent_fn_prime_cache(3);
assert_eq!(
val, 33,
"companions_vis on #[concurrent_cached]: prime_cache companion returned wrong value"
);
}
#[concurrent_cached(key = "u32", convert = { n })]
pub fn default_companions_vis_concurrent_fn(n: u32) -> u32 {
n + 10
}
#[test]
fn test_companions_vis_concurrent_default_inherits_fn_visibility() {
let val = default_companions_vis_concurrent_fn_prime_cache(7);
assert_eq!(
val, 17,
"default companions_vis on #[concurrent_cached]: prime_cache companion returned wrong value"
);
}
}
static CONCRETE_ONCE_CALLS: AtomicUsize = AtomicUsize::new(0);
#[once]
fn generic_once_concrete_return<T: std::fmt::Debug>(_x: T) -> usize {
CONCRETE_ONCE_CALLS.fetch_add(1, Ordering::SeqCst);
42
}
#[test]
fn generic_once_concrete_value_type_compiles_and_caches() {
CONCRETE_ONCE_CALLS.store(0, Ordering::SeqCst);
assert_eq!(generic_once_concrete_return::<i32>(1), 42);
assert_eq!(CONCRETE_ONCE_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(
generic_once_concrete_return::<String>("hello".to_string()),
42
);
assert_eq!(
CONCRETE_ONCE_CALLS.load(Ordering::SeqCst),
1,
"#[once] with concrete return type: subsequent calls must be cache hits"
);
}
static NAMED_ONCE_CALLS: AtomicUsize = AtomicUsize::new(0);
#[once(name = "MY_CUSTOM_ONCE_CACHE")]
fn named_once_fn() -> usize {
NAMED_ONCE_CALLS.fetch_add(1, Ordering::SeqCst);
99
}
#[test]
fn once_valid_name_compiles_and_caches() {
NAMED_ONCE_CALLS.store(0, Ordering::SeqCst);
assert_eq!(named_once_fn(), 99);
assert_eq!(NAMED_ONCE_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(named_once_fn(), 99);
assert_eq!(
NAMED_ONCE_CALLS.load(Ordering::SeqCst),
1,
"valid custom name on #[once]: second call must be a cache hit"
);
}
static ARC_CACHED_CALLS: AtomicUsize = AtomicUsize::new(0);
static ARC_ONCE_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached]
fn arc_cached_fn(n: usize) -> std::sync::Arc<Vec<usize>> {
ARC_CACHED_CALLS.fetch_add(1, Ordering::SeqCst);
std::sync::Arc::new((0..n).collect())
}
#[once]
fn arc_once_fn() -> std::sync::Arc<Vec<usize>> {
ARC_ONCE_CALLS.fetch_add(1, Ordering::SeqCst);
std::sync::Arc::new(vec![1, 2, 3])
}
#[test]
fn returning_arc_hands_back_the_same_allocation_on_a_hit() {
ARC_CACHED_CALLS.store(0, Ordering::SeqCst);
let first = arc_cached_fn(4);
let second = arc_cached_fn(4);
assert_eq!(
ARC_CACHED_CALLS.load(Ordering::SeqCst),
1,
"#[cached] returning Arc: the second call must be a cache hit"
);
assert!(
std::sync::Arc::ptr_eq(&first, &second),
"#[cached] returning Arc: a hit must clone the pointer, not the Vec"
);
let other = arc_cached_fn(5);
assert!(!std::sync::Arc::ptr_eq(&first, &other));
}
#[test]
fn returning_arc_from_once_hands_back_the_same_allocation() {
ARC_ONCE_CALLS.store(0, Ordering::SeqCst);
let first = arc_once_fn();
let second = arc_once_fn();
assert_eq!(
ARC_ONCE_CALLS.load(Ordering::SeqCst),
1,
"#[once] returning Arc: the second call must be a cache hit"
);
assert!(
std::sync::Arc::ptr_eq(&first, &second),
"#[once] returning Arc: a hit must clone the pointer, not the Vec"
);
}