#![cfg(feature = "std")]
use std::sync::Arc;
use std::thread;
use uselesskey_core_cache::ArtifactCache;
use uselesskey_core_id::{ArtifactId, DerivationVersion};
fn make_id(label: &str) -> ArtifactId {
ArtifactId::new(
"domain:test",
label,
b"spec",
"default",
DerivationVersion::V1,
)
}
#[test]
fn concurrent_insert_if_absent_all_see_same_value() {
let cache = Arc::new(ArtifactCache::new());
let id = make_id("shared");
let handles: Vec<_> = (0..32)
.map(|i| {
let cache = Arc::clone(&cache);
let id = id.clone();
thread::spawn(move || {
let val = Arc::new(i as u64);
let result = cache.insert_if_absent_typed::<u64>(id, val);
*result
})
})
.collect();
let results: Vec<u64> = handles.into_iter().map(|h| h.join().unwrap()).collect();
assert!(results.windows(2).all(|w| w[0] == w[1]));
}
#[test]
fn concurrent_reads_and_writes_no_panic() {
let cache = Arc::new(ArtifactCache::new());
for i in 0..100 {
let id = make_id(&format!("key-{i}"));
cache.insert_if_absent_typed::<u64>(id, Arc::new(i as u64));
}
let handles: Vec<_> = (0..16)
.map(|t| {
let cache = Arc::clone(&cache);
thread::spawn(move || {
for i in 0..100 {
let id = make_id(&format!("key-{i}"));
if t % 2 == 0 {
let val = cache.get_typed::<u64>(&id);
assert!(val.is_some());
} else {
let val = cache.insert_if_absent_typed::<u64>(id, Arc::new(999));
assert_eq!(*val, i as u64); }
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
}
#[test]
fn get_from_empty_cache_returns_none() {
let cache = ArtifactCache::new();
let id = make_id("nonexistent");
assert!(cache.get_typed::<u64>(&id).is_none());
}
#[test]
fn empty_cache_len_is_zero() {
let cache = ArtifactCache::new();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
fn cache_handles_many_entries() {
let cache = ArtifactCache::new();
for i in 0..1000 {
let id = make_id(&format!("entry-{i}"));
cache.insert_if_absent_typed::<u64>(id, Arc::new(i));
}
assert_eq!(cache.len(), 1000);
for i in 0..1000 {
let id = make_id(&format!("entry-{i}"));
let val = cache.get_typed::<u64>(&id).unwrap();
assert_eq!(*val, i);
}
}
#[test]
fn clear_while_reading_does_not_panic() {
let cache = Arc::new(ArtifactCache::new());
for i in 0..50 {
let id = make_id(&format!("item-{i}"));
cache.insert_if_absent_typed::<u64>(id, Arc::new(i as u64));
}
let handles: Vec<_> = (0..8)
.map(|t| {
let cache = Arc::clone(&cache);
thread::spawn(move || {
for _ in 0..100 {
if t == 0 {
cache.clear();
} else {
let id = make_id(&format!("item-{}", t % 50));
let _ = cache.get_typed::<u64>(&id); }
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
}
#[test]
fn cache_with_unicode_label() {
let cache = ArtifactCache::new();
let id = make_id("日本語🔑");
cache.insert_if_absent_typed::<String>(id.clone(), Arc::new("value".to_string()));
let val = cache.get_typed::<String>(&id).unwrap();
assert_eq!(&*val, "value");
}
#[test]
fn cache_with_empty_label() {
let cache = ArtifactCache::new();
let id = make_id("");
cache.insert_if_absent_typed::<u64>(id.clone(), Arc::new(42));
assert_eq!(*cache.get_typed::<u64>(&id).unwrap(), 42);
}
#[test]
fn debug_shows_type_and_count() {
let cache = ArtifactCache::new();
let dbg = format!("{cache:?}");
assert!(dbg.contains("ArtifactCache"));
assert!(dbg.contains("0"), "should show count of zero");
}
#[test]
fn debug_after_inserts_shows_count() {
let cache = ArtifactCache::new();
cache.insert_if_absent_typed::<u64>(make_id("a"), Arc::new(1));
cache.insert_if_absent_typed::<u64>(make_id("b"), Arc::new(2));
let dbg = format!("{cache:?}");
assert!(dbg.contains("2"), "should show count of 2");
}