#![cfg(feature = "proc_macro")]
#![allow(clippy::needless_lifetimes)]
use std::sync::atomic::{AtomicUsize, Ordering};
use cached::macros::cached;
static UNSYNC_FR_PREDICATE_EVALS: AtomicUsize = AtomicUsize::new(0);
static UNSYNC_FR_BODY_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(
key = "i32",
convert = "{ x }",
sync_writes = "default",
unsync_reads,
force_refresh = "{ UNSYNC_FR_PREDICATE_EVALS.fetch_add(1, Ordering::SeqCst); bypass }"
)]
fn unsync_fr_fn(x: i32, bypass: bool) -> i32 {
let _ = bypass; UNSYNC_FR_BODY_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn force_refresh_predicate_evaluated_once_per_call_on_unsync_reads_path() {
UNSYNC_FR_PREDICATE_EVALS.store(0, Ordering::SeqCst);
UNSYNC_FR_BODY_CALLS.store(0, Ordering::SeqCst);
let _ = unsync_fr_fn(42, false);
assert_eq!(
UNSYNC_FR_PREDICATE_EVALS.load(Ordering::SeqCst),
1,
"call 1 (miss, no bypass): predicate must run exactly once"
);
assert_eq!(UNSYNC_FR_BODY_CALLS.load(Ordering::SeqCst), 1);
let _ = unsync_fr_fn(42, false);
assert_eq!(
UNSYNC_FR_PREDICATE_EVALS.load(Ordering::SeqCst),
2,
"call 2 (hit, no bypass): predicate must run exactly once (not twice)"
);
assert_eq!(
UNSYNC_FR_BODY_CALLS.load(Ordering::SeqCst),
1,
"hit path: body must not re-run"
);
let _ = unsync_fr_fn(42, true);
assert_eq!(
UNSYNC_FR_PREDICATE_EVALS.load(Ordering::SeqCst),
3,
"call 3 (bypass): predicate must run exactly once"
);
assert_eq!(
UNSYNC_FR_BODY_CALLS.load(Ordering::SeqCst),
2,
"bypass: body must re-run"
);
assert_eq!(
UNSYNC_FR_PREDICATE_EVALS.load(Ordering::SeqCst),
3,
"total predicate evals must equal total calls (1 eval per call)"
);
}
static CONST_GEN_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(key = "String", convert = r#"{ format!("{:?}", arr) }"#)]
fn const_generic_cached<const N: usize>(arr: &[i32; N]) -> usize {
CONST_GEN_CALLS.fetch_add(1, Ordering::SeqCst);
arr.len()
}
#[test]
fn const_generic_with_key_convert_compiles_and_caches() {
CONST_GEN_CALLS.store(0, Ordering::SeqCst);
let arr = [1i32, 2, 3];
let v1 = const_generic_cached(&arr);
assert_eq!(v1, 3);
assert_eq!(CONST_GEN_CALLS.load(Ordering::SeqCst), 1);
let v2 = const_generic_cached(&arr);
assert_eq!(v2, 3);
assert_eq!(
CONST_GEN_CALLS.load(Ordering::SeqCst),
1,
"second call with same key must be a cache hit"
);
}
use cached::macros::concurrent_cached;
static LIFETIME_CACHED_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(key = "String", convert = r#"{ s.to_owned() }"#)]
fn lifetime_only_cached<'a>(s: &'a str) -> usize {
LIFETIME_CACHED_CALLS.fetch_add(1, Ordering::SeqCst);
s.len()
}
#[test]
fn lifetime_only_generic_cached_compiles_and_caches() {
LIFETIME_CACHED_CALLS.store(0, Ordering::SeqCst);
assert_eq!(lifetime_only_cached("hello"), 5);
assert_eq!(LIFETIME_CACHED_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(lifetime_only_cached("hello"), 5);
assert_eq!(
LIFETIME_CACHED_CALLS.load(Ordering::SeqCst),
1,
"second call with same arg must be a cache hit (lifetime generic must not be rejected)"
);
assert_eq!(lifetime_only_cached("world!"), 6);
assert_eq!(LIFETIME_CACHED_CALLS.load(Ordering::SeqCst), 2);
}
static LIFETIME_CONC_CALLS: AtomicUsize = AtomicUsize::new(0);
#[concurrent_cached(key = "String", convert = r#"{ s.to_owned() }"#)]
fn lifetime_only_concurrent<'a>(s: &'a str) -> usize {
LIFETIME_CONC_CALLS.fetch_add(1, Ordering::SeqCst);
s.len()
}
#[test]
fn lifetime_only_generic_concurrent_cached_compiles_and_caches() {
LIFETIME_CONC_CALLS.store(0, Ordering::SeqCst);
assert_eq!(lifetime_only_concurrent("abc"), 3);
assert_eq!(LIFETIME_CONC_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(lifetime_only_concurrent("abc"), 3);
assert_eq!(
LIFETIME_CONC_CALLS.load(Ordering::SeqCst),
1,
"second call with same arg must be a cache hit (lifetime generic must not be rejected)"
);
assert_eq!(lifetime_only_concurrent("xy"), 2);
assert_eq!(LIFETIME_CONC_CALLS.load(Ordering::SeqCst), 2);
}
static UNSYNC_NO_FR_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(unsync_reads, sync_writes = "default")]
fn unsync_no_force_refresh(x: i32) -> i32 {
UNSYNC_NO_FR_CALLS.fetch_add(1, Ordering::SeqCst);
x * 2
}
#[test]
fn unsync_reads_without_force_refresh_caches_correctly() {
UNSYNC_NO_FR_CALLS.store(0, Ordering::SeqCst);
assert_eq!(unsync_no_force_refresh(3), 6);
assert_eq!(UNSYNC_NO_FR_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(unsync_no_force_refresh(3), 6);
assert_eq!(
UNSYNC_NO_FR_CALLS.load(Ordering::SeqCst),
1,
"unsync_reads without force_refresh: second call must be a cache hit"
);
assert_eq!(unsync_no_force_refresh(7), 14);
assert_eq!(UNSYNC_NO_FR_CALLS.load(Ordering::SeqCst), 2);
assert_eq!(unsync_no_force_refresh(7), 14);
assert_eq!(
UNSYNC_NO_FR_CALLS.load(Ordering::SeqCst),
2,
"unsync_reads without force_refresh: repeated call must stay cached"
);
}
static UNSYNC_ALWAYS_FR_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(
key = "i32",
convert = "{ x }",
unsync_reads,
sync_writes = "default",
force_refresh = "{ true }"
)]
fn unsync_always_force_refresh(x: i32) -> i32 {
UNSYNC_ALWAYS_FR_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn unsync_reads_with_always_true_force_refresh_recomputes_every_call() {
UNSYNC_ALWAYS_FR_CALLS.store(0, Ordering::SeqCst);
let _ = unsync_always_force_refresh(5);
let _ = unsync_always_force_refresh(5);
let _ = unsync_always_force_refresh(5);
assert_eq!(
UNSYNC_ALWAYS_FR_CALLS.load(Ordering::SeqCst),
3,
"always-bypass predicate: body must run on every call (3 calls -> 3 body executions)"
);
}
static BY_KEY_UNSYNC_FR_PREDICATE_EVALS: AtomicUsize = AtomicUsize::new(0);
static BY_KEY_UNSYNC_FR_BODY_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(
key = "i32",
convert = "{ x }",
sync_writes = "by_key",
unsync_reads,
force_refresh = "{ BY_KEY_UNSYNC_FR_PREDICATE_EVALS.fetch_add(1, Ordering::SeqCst); bypass }"
)]
fn by_key_unsync_force_refresh(x: i32, bypass: bool) -> i32 {
let _ = bypass; BY_KEY_UNSYNC_FR_BODY_CALLS.fetch_add(1, Ordering::SeqCst);
x
}
#[test]
fn by_key_unsync_reads_force_refresh_single_eval_per_call() {
BY_KEY_UNSYNC_FR_PREDICATE_EVALS.store(0, Ordering::SeqCst);
BY_KEY_UNSYNC_FR_BODY_CALLS.store(0, Ordering::SeqCst);
let _ = by_key_unsync_force_refresh(10, false);
assert_eq!(
BY_KEY_UNSYNC_FR_PREDICATE_EVALS.load(Ordering::SeqCst),
1,
"by_key + unsync: miss, no bypass: predicate must run exactly once"
);
assert_eq!(BY_KEY_UNSYNC_FR_BODY_CALLS.load(Ordering::SeqCst), 1);
let _ = by_key_unsync_force_refresh(10, false);
assert_eq!(
BY_KEY_UNSYNC_FR_PREDICATE_EVALS.load(Ordering::SeqCst),
2,
"by_key + unsync: hit, no bypass: predicate must run exactly once"
);
assert_eq!(
BY_KEY_UNSYNC_FR_BODY_CALLS.load(Ordering::SeqCst),
1,
"hit path: body must not re-run"
);
let _ = by_key_unsync_force_refresh(10, true);
assert_eq!(
BY_KEY_UNSYNC_FR_PREDICATE_EVALS.load(Ordering::SeqCst),
3,
"by_key + unsync: bypass: predicate must run exactly once"
);
assert_eq!(
BY_KEY_UNSYNC_FR_BODY_CALLS.load(Ordering::SeqCst),
2,
"bypass: body must re-run"
);
assert_eq!(
BY_KEY_UNSYNC_FR_PREDICATE_EVALS.load(Ordering::SeqCst),
3,
"total predicate evals must equal total calls (1 eval per call on by_key path)"
);
}
#[cfg(feature = "time_stores")]
mod result_fallback_live_tests {
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use cached::macros::cached;
static RF_LIVE_BODY_CALLS: AtomicUsize = AtomicUsize::new(0);
static RF_LIVE_RETURN_ERR: AtomicBool = AtomicBool::new(false);
#[cached(
key = "i32",
convert = "{ x }",
ttl_secs = 60,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn rf_live_fn(x: i32, bypass: bool) -> Result<i32, String> {
let _ = bypass; RF_LIVE_BODY_CALLS.fetch_add(1, Ordering::SeqCst);
if RF_LIVE_RETURN_ERR.load(Ordering::SeqCst) {
Err(format!("error for {x}"))
} else {
Ok(x * 10)
}
}
#[test]
fn result_fallback_force_refresh_live_entry_returns_stale_ok() {
RF_LIVE_BODY_CALLS.store(0, Ordering::SeqCst);
RF_LIVE_RETURN_ERR.store(false, Ordering::SeqCst);
let first = rf_live_fn(100, false);
assert_eq!(first, Ok(1000), "seed call must return Ok(1000)");
assert_eq!(RF_LIVE_BODY_CALLS.load(Ordering::SeqCst), 1);
RF_LIVE_RETURN_ERR.store(true, Ordering::SeqCst);
let second = rf_live_fn(100, true);
assert_eq!(
second,
Ok(1000),
"force-refresh Err over a LIVE entry must return stale Ok fallback"
);
assert_eq!(RF_LIVE_BODY_CALLS.load(Ordering::SeqCst), 2);
}
}
#[cfg(feature = "time_stores")]
mod result_fallback_expired_tests {
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use cached::macros::cached;
static RF_EXPIRED_BODY_CALLS: AtomicUsize = AtomicUsize::new(0);
static RF_EXPIRED_RETURN_ERR: AtomicBool = AtomicBool::new(false);
#[cached(
key = "i32",
convert = "{ x }",
ttl_secs = 1,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn rf_expired_fn(x: i32, bypass: bool) -> Result<i32, String> {
let _ = bypass; RF_EXPIRED_BODY_CALLS.fetch_add(1, Ordering::SeqCst);
if RF_EXPIRED_RETURN_ERR.load(Ordering::SeqCst) {
Err(format!("error for {x}"))
} else {
Ok(x * 10)
}
}
#[test]
fn result_fallback_force_refresh_expired_entry_returns_stale_ok() {
RF_EXPIRED_BODY_CALLS.store(0, Ordering::SeqCst);
RF_EXPIRED_RETURN_ERR.store(false, Ordering::SeqCst);
let first = rf_expired_fn(200, false);
assert_eq!(first, Ok(2000), "seed call must return Ok(2000)");
assert_eq!(RF_EXPIRED_BODY_CALLS.load(Ordering::SeqCst), 1);
std::thread::sleep(std::time::Duration::from_millis(1200));
RF_EXPIRED_RETURN_ERR.store(true, Ordering::SeqCst);
let second = rf_expired_fn(200, true);
assert_eq!(
second,
Ok(2000),
"force-refresh Err over an EXPIRED entry must return stale Ok fallback (regression)"
);
assert_eq!(RF_EXPIRED_BODY_CALLS.load(Ordering::SeqCst), 2);
}
}
#[cfg(feature = "time_stores")]
mod result_fallback_lru_ttl_tests {
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use cached::macros::cached;
static RF_LRUTTL_EXP_BODY_CALLS: AtomicUsize = AtomicUsize::new(0);
static RF_LRUTTL_EXP_RETURN_ERR: AtomicBool = AtomicBool::new(false);
#[cached(
key = "i32",
convert = "{ x }",
ttl_secs = 1,
max_size = 16,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn rf_lru_ttl_expired_fn(x: i32, bypass: bool) -> Result<i32, String> {
let _ = bypass;
RF_LRUTTL_EXP_BODY_CALLS.fetch_add(1, Ordering::SeqCst);
if RF_LRUTTL_EXP_RETURN_ERR.load(Ordering::SeqCst) {
Err(format!("error for {x}"))
} else {
Ok(x * 10)
}
}
#[test]
fn lru_ttl_force_refresh_expired_entry_returns_stale_ok() {
RF_LRUTTL_EXP_BODY_CALLS.store(0, Ordering::SeqCst);
RF_LRUTTL_EXP_RETURN_ERR.store(false, Ordering::SeqCst);
assert_eq!(rf_lru_ttl_expired_fn(300, false), Ok(3000), "seed call");
assert_eq!(RF_LRUTTL_EXP_BODY_CALLS.load(Ordering::SeqCst), 1);
std::thread::sleep(std::time::Duration::from_millis(1200));
RF_LRUTTL_EXP_RETURN_ERR.store(true, Ordering::SeqCst);
assert_eq!(
rf_lru_ttl_expired_fn(300, true),
Ok(3000),
"LruTtlCache: force-refresh Err over expired entry must return stale Ok"
);
assert_eq!(RF_LRUTTL_EXP_BODY_CALLS.load(Ordering::SeqCst), 2);
}
static RF_LRUTTL_LIVE_BODY_CALLS: AtomicUsize = AtomicUsize::new(0);
static RF_LRUTTL_LIVE_RETURN_ERR: AtomicBool = AtomicBool::new(false);
#[cached(
key = "i32",
convert = "{ x }",
ttl_secs = 60,
max_size = 16,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn rf_lru_ttl_live_fn(x: i32, bypass: bool) -> Result<i32, String> {
let _ = bypass;
RF_LRUTTL_LIVE_BODY_CALLS.fetch_add(1, Ordering::SeqCst);
if RF_LRUTTL_LIVE_RETURN_ERR.load(Ordering::SeqCst) {
Err(format!("error for {x}"))
} else {
Ok(x * 10)
}
}
#[test]
fn lru_ttl_force_refresh_live_entry_returns_stale_ok() {
RF_LRUTTL_LIVE_BODY_CALLS.store(0, Ordering::SeqCst);
RF_LRUTTL_LIVE_RETURN_ERR.store(false, Ordering::SeqCst);
assert_eq!(rf_lru_ttl_live_fn(301, false), Ok(3010), "seed call");
assert_eq!(RF_LRUTTL_LIVE_BODY_CALLS.load(Ordering::SeqCst), 1);
RF_LRUTTL_LIVE_RETURN_ERR.store(true, Ordering::SeqCst);
assert_eq!(
rf_lru_ttl_live_fn(301, true),
Ok(3010),
"LruTtlCache: force-refresh Err over live entry must return stale Ok"
);
assert_eq!(RF_LRUTTL_LIVE_BODY_CALLS.load(Ordering::SeqCst), 2);
}
}
#[cfg(feature = "time_stores")]
mod result_fallback_expiring_tests {
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use cached::Expires;
use cached::macros::cached;
#[derive(Clone)]
struct Val {
n: i32,
expired: bool,
}
impl Expires for Val {
fn is_expired(&self) -> bool {
self.expired
}
}
static RF_EXP_E_BODY_CALLS: AtomicUsize = AtomicUsize::new(0);
static RF_EXP_E_RETURN_ERR: AtomicBool = AtomicBool::new(false);
#[cached(
key = "i32",
convert = "{ x }",
expires = true,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn rf_expiring_expired_fn(x: i32, bypass: bool) -> Result<Val, String> {
let _ = bypass;
RF_EXP_E_BODY_CALLS.fetch_add(1, Ordering::SeqCst);
if RF_EXP_E_RETURN_ERR.load(Ordering::SeqCst) {
Err(format!("error for {x}"))
} else {
Ok(Val {
n: x * 10,
expired: true,
})
}
}
#[test]
fn expiring_force_refresh_expired_entry_returns_stale_ok() {
RF_EXP_E_BODY_CALLS.store(0, Ordering::SeqCst);
RF_EXP_E_RETURN_ERR.store(false, Ordering::SeqCst);
let first = rf_expiring_expired_fn(400, false).expect("seed Ok");
assert_eq!(first.n, 4000);
assert_eq!(RF_EXP_E_BODY_CALLS.load(Ordering::SeqCst), 1);
RF_EXP_E_RETURN_ERR.store(true, Ordering::SeqCst);
let second =
rf_expiring_expired_fn(400, true).expect("expired-entry fallback must yield Ok");
assert_eq!(
second.n, 4000,
"ExpiringCache: force-refresh Err over expired entry must return stale Ok"
);
assert_eq!(RF_EXP_E_BODY_CALLS.load(Ordering::SeqCst), 2);
}
static RF_EXP_L_BODY_CALLS: AtomicUsize = AtomicUsize::new(0);
static RF_EXP_L_RETURN_ERR: AtomicBool = AtomicBool::new(false);
#[cached(
key = "i32",
convert = "{ x }",
expires = true,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn rf_expiring_live_fn(x: i32, bypass: bool) -> Result<Val, String> {
let _ = bypass;
RF_EXP_L_BODY_CALLS.fetch_add(1, Ordering::SeqCst);
if RF_EXP_L_RETURN_ERR.load(Ordering::SeqCst) {
Err(format!("error for {x}"))
} else {
Ok(Val {
n: x * 10,
expired: false,
}) }
}
#[test]
fn expiring_force_refresh_live_entry_returns_stale_ok() {
RF_EXP_L_BODY_CALLS.store(0, Ordering::SeqCst);
RF_EXP_L_RETURN_ERR.store(false, Ordering::SeqCst);
let first = rf_expiring_live_fn(401, false).expect("seed Ok");
assert_eq!(first.n, 4010);
assert_eq!(RF_EXP_L_BODY_CALLS.load(Ordering::SeqCst), 1);
RF_EXP_L_RETURN_ERR.store(true, Ordering::SeqCst);
let second = rf_expiring_live_fn(401, true).expect("live-entry fallback must yield Ok");
assert_eq!(
second.n, 4010,
"ExpiringCache: force-refresh Err over live entry must return stale Ok"
);
assert_eq!(RF_EXP_L_BODY_CALLS.load(Ordering::SeqCst), 2);
}
}
#[cfg(feature = "time_stores")]
mod result_fallback_expiring_lru_tests {
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use cached::Expires;
use cached::macros::cached;
static RF_EXPLRU_BODY_CALLS: AtomicUsize = AtomicUsize::new(0);
static RF_EXPLRU_RETURN_ERR: AtomicBool = AtomicBool::new(false);
static RF_EXPLRU_MAKE_STALE: AtomicBool = AtomicBool::new(false);
#[derive(Clone)]
struct Val {
n: i32,
expired: bool,
}
impl Expires for Val {
fn is_expired(&self) -> bool {
self.expired
}
}
#[cached(
key = "i32",
convert = "{ x }",
expires = true,
max_size = 16,
result_fallback = true,
force_refresh = "{ bypass }"
)]
fn rf_expiring_lru_fn(x: i32, bypass: bool) -> Result<Val, String> {
let _ = bypass;
RF_EXPLRU_BODY_CALLS.fetch_add(1, Ordering::SeqCst);
if RF_EXPLRU_RETURN_ERR.load(Ordering::SeqCst) {
Err(format!("error for {x}"))
} else {
Ok(Val {
n: x * 10,
expired: RF_EXPLRU_MAKE_STALE.load(Ordering::SeqCst),
})
}
}
#[test]
fn expiring_lru_force_refresh_expired_entry_returns_stale_ok() {
RF_EXPLRU_BODY_CALLS.store(0, Ordering::SeqCst);
RF_EXPLRU_RETURN_ERR.store(false, Ordering::SeqCst);
RF_EXPLRU_MAKE_STALE.store(true, Ordering::SeqCst);
let first = rf_expiring_lru_fn(500, false).expect("seed Ok");
assert_eq!(first.n, 5000);
assert_eq!(RF_EXPLRU_BODY_CALLS.load(Ordering::SeqCst), 1);
RF_EXPLRU_RETURN_ERR.store(true, Ordering::SeqCst);
let second = rf_expiring_lru_fn(500, true).expect("expired-entry fallback must yield Ok");
assert_eq!(
second.n, 5000,
"ExpiringLruCache: force-refresh Err over expired entry must return stale Ok"
);
assert_eq!(RF_EXPLRU_BODY_CALLS.load(Ordering::SeqCst), 2);
}
}
#[cfg(feature = "time_stores")]
mod result_fallback_predicate_false_tests {
use std::sync::atomic::{AtomicUsize, Ordering};
use cached::macros::cached;
static RF_FALSE_BODY_CALLS: AtomicUsize = AtomicUsize::new(0);
#[cached(
key = "i32",
convert = "{ x }",
ttl_secs = 60,
result_fallback = true,
force_refresh = "{ false }"
)]
fn rf_false_fn(x: i32) -> Result<i32, String> {
RF_FALSE_BODY_CALLS.fetch_add(1, Ordering::SeqCst);
Ok(x * 10)
}
#[test]
fn predicate_false_serves_cache_without_rerunning_body() {
RF_FALSE_BODY_CALLS.store(0, Ordering::SeqCst);
assert_eq!(rf_false_fn(600), Ok(6000), "miss seeds the cache");
assert_eq!(RF_FALSE_BODY_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(rf_false_fn(600), Ok(6000));
assert_eq!(
RF_FALSE_BODY_CALLS.load(Ordering::SeqCst),
1,
"predicate-false fresh hit must serve cache, not re-run body"
);
}
}
#[cfg(all(feature = "async", feature = "time_stores"))]
mod result_fallback_async_tests {
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use cached::macros::cached;
static RF_ASYNC_BODY_CALLS: AtomicUsize = AtomicUsize::new(0);
static RF_ASYNC_RETURN_ERR: AtomicBool = AtomicBool::new(false);
#[cached(
key = "i32",
convert = "{ x }",
ttl_secs = 1,
result_fallback = true,
force_refresh = "{ bypass }"
)]
async fn rf_async_fn(x: i32, bypass: bool) -> Result<i32, String> {
let _ = bypass;
RF_ASYNC_BODY_CALLS.fetch_add(1, Ordering::SeqCst);
if RF_ASYNC_RETURN_ERR.load(Ordering::SeqCst) {
Err(format!("error for {x}"))
} else {
Ok(x * 10)
}
}
#[tokio::test]
async fn async_force_refresh_expired_entry_returns_stale_ok() {
RF_ASYNC_BODY_CALLS.store(0, Ordering::SeqCst);
RF_ASYNC_RETURN_ERR.store(false, Ordering::SeqCst);
assert_eq!(rf_async_fn(700, false).await, Ok(7000), "seed call");
assert_eq!(RF_ASYNC_BODY_CALLS.load(Ordering::SeqCst), 1);
tokio::time::sleep(std::time::Duration::from_millis(1200)).await;
RF_ASYNC_RETURN_ERR.store(true, Ordering::SeqCst);
assert_eq!(
rf_async_fn(700, true).await,
Ok(7000),
"async: force-refresh Err over expired entry must return stale Ok"
);
assert_eq!(RF_ASYNC_BODY_CALLS.load(Ordering::SeqCst), 2);
}
}