use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use tempfile::TempDir;
use crate::cache::engine_profile::{CpuPolicy, EngineProfile};
use crate::cache::keys::ArtifactKey;
use crate::cache::{CacheConfig, CacheManager};
fn fixture_bytes() -> Vec<u8> {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/fixtures/packs/secrets_store_smoke/components/echo_secret.wasm");
std::fs::read(path).expect("fixture wasm")
}
fn build_key(engine: &wasmtime::Engine) -> ArtifactKey {
let profile = EngineProfile::from_engine(engine, CpuPolicy::Native, "default".to_string());
ArtifactKey::new(profile.id().to_string(), "sha256:test".to_string())
}
#[tokio::test]
async fn singleflight_compiles_once() {
let temp = TempDir::new().expect("temp dir");
let engine = Arc::new(wasmtime::Engine::default());
let profile = EngineProfile::from_engine(&engine, CpuPolicy::Native, "default".to_string());
let config = CacheConfig {
root: temp.path().to_path_buf(),
disk_enabled: false,
memory_enabled: true,
memory_max_bytes: 1024 * 1024,
..CacheConfig::default()
};
let cache = Arc::new(CacheManager::new(config, profile));
let key = build_key(&engine);
let bytes = fixture_bytes();
let counter = Arc::new(AtomicU64::new(0));
let mut tasks = Vec::new();
for _ in 0..8 {
let cache = Arc::clone(&cache);
let key = key.clone();
let bytes = bytes.clone();
let counter = Arc::clone(&counter);
let engine = Arc::clone(&engine);
tasks.push(tokio::spawn(async move {
let _ = cache
.get_component(engine.as_ref(), &key, move || {
counter.fetch_add(1, Ordering::SeqCst);
Ok(bytes)
})
.await
.expect("component");
}));
}
for task in tasks {
task.await.expect("task");
}
assert_eq!(counter.load(Ordering::SeqCst), 1);
assert_eq!(cache.metrics().compiles, 1);
}
#[tokio::test]
async fn disk_hit_skips_compile() {
let temp = TempDir::new().expect("temp dir");
let engine = Arc::new(wasmtime::Engine::default());
let profile = EngineProfile::from_engine(&engine, CpuPolicy::Native, "default".to_string());
let config = CacheConfig {
root: temp.path().to_path_buf(),
disk_enabled: true,
memory_enabled: false,
..CacheConfig::default()
};
let cache = CacheManager::new(config, profile);
let key = build_key(&engine);
let bytes = fixture_bytes();
let counter = Arc::new(AtomicU64::new(0));
let _ = cache
.get_component(engine.as_ref(), &key, {
let bytes = bytes.clone();
let counter = Arc::clone(&counter);
move || {
counter.fetch_add(1, Ordering::SeqCst);
Ok(bytes)
}
})
.await
.expect("component");
let _ = cache
.get_component(engine.as_ref(), &key, {
let bytes = bytes.clone();
let counter = Arc::clone(&counter);
move || {
counter.fetch_add(1, Ordering::SeqCst);
Ok(bytes)
}
})
.await
.expect("component");
assert_eq!(counter.load(Ordering::SeqCst), 1);
assert!(cache.metrics().disk_hits >= 1);
}
#[tokio::test]
async fn memory_hit_skips_disk() {
let temp = TempDir::new().expect("temp dir");
let engine = Arc::new(wasmtime::Engine::default());
let profile = EngineProfile::from_engine(&engine, CpuPolicy::Native, "default".to_string());
let config = CacheConfig {
root: temp.path().to_path_buf(),
disk_enabled: true,
memory_enabled: true,
memory_max_bytes: 1024 * 1024,
..CacheConfig::default()
};
let cache = CacheManager::new(config, profile);
let key = build_key(&engine);
let bytes = fixture_bytes();
let _ = cache
.get_component(engine.as_ref(), &key, {
let bytes = bytes.clone();
move || Ok(bytes)
})
.await
.expect("component");
let before = cache.metrics();
let _ = cache
.get_component(engine.as_ref(), &key, {
let bytes = bytes.clone();
move || Ok(bytes)
})
.await
.expect("component");
let after = cache.metrics();
assert!(after.memory_hits > before.memory_hits);
assert_eq!(after.disk_reads, before.disk_reads);
}
#[tokio::test]
async fn warmup_persists_and_hits_disk() {
let temp = TempDir::new().expect("temp dir");
let engine = Arc::new(wasmtime::Engine::default());
let profile =
EngineProfile::from_engine(engine.as_ref(), CpuPolicy::Native, "default".to_string());
let config = CacheConfig {
root: temp.path().to_path_buf(),
disk_enabled: true,
memory_enabled: false,
..CacheConfig::default()
};
let cache = CacheManager::new(config.clone(), profile.clone());
let key = ArtifactKey::new(profile.id().to_string(), "sha256:warmup".to_string());
let bytes = fixture_bytes();
let _ = cache
.get_component(engine.as_ref(), &key, {
let bytes = bytes.clone();
move || Ok(bytes)
})
.await
.expect("component");
let disk_root = config.disk_root(profile.id());
let artifact_path = disk_root.join("artifacts/sha256_warmup.cwasm");
assert!(artifact_path.exists());
let cache_again = CacheManager::new(config, profile);
let before = cache_again.metrics();
let _ = cache_again
.get_component(engine.as_ref(), &key, {
let bytes = bytes.clone();
move || Ok(bytes)
})
.await
.expect("component");
let after = cache_again.metrics();
assert!(after.disk_hits > before.disk_hits);
assert_eq!(after.compiles, before.compiles);
}
#[tokio::test]
async fn doctor_warmup_and_prune_report_expected_defaults() {
let temp = TempDir::new().expect("temp dir");
let engine = wasmtime::Engine::default();
let profile = EngineProfile::from_engine(&engine, CpuPolicy::Native, "default".to_string());
let config = CacheConfig {
root: temp.path().to_path_buf(),
disk_enabled: false,
memory_enabled: true,
memory_max_bytes: 1024,
..CacheConfig::default()
};
let cache = CacheManager::new(config, profile.clone());
let key = ArtifactKey::new(profile.id().to_string(), "sha256:doctor".to_string());
assert_eq!(cache.engine_profile_id(), profile.id());
assert_eq!(cache.disk_stats().expect("disk stats").artifact_count, 0);
let warmup = cache
.warmup(
&engine,
&[crate::cache::WarmupItem {
key,
bytes: fixture_bytes(),
}],
crate::cache::WarmupMode::BestEffort,
)
.await
.expect("warmup");
assert_eq!(warmup.warmed, 1);
assert_eq!(warmup.skipped, 0);
let doctor = cache.doctor();
assert!(!doctor.disk_enabled);
assert!(doctor.memory_enabled);
assert_eq!(doctor.entries_checked, 0);
let prune = cache.prune_disk(true).await.expect("prune");
assert_eq!(prune.removed_entries, 0);
}
#[tokio::test]
async fn warmup_writes_cwasm_to_disk_and_skips_existing() {
let temp = TempDir::new().expect("temp dir");
let engine = wasmtime::Engine::default();
let profile = EngineProfile::from_engine(&engine, CpuPolicy::Native, "default".to_string());
let config = CacheConfig {
root: temp.path().to_path_buf(),
disk_enabled: true,
memory_enabled: false,
..CacheConfig::default()
};
let cache = CacheManager::new(config.clone(), profile.clone());
let key = ArtifactKey::new(
profile.id().to_string(),
"sha256:warmup-precompile".to_string(),
);
let bytes = fixture_bytes();
let report = cache
.warmup(
&engine,
&[crate::cache::WarmupItem {
key: key.clone(),
bytes: bytes.clone(),
}],
crate::cache::WarmupMode::Strict,
)
.await
.expect("warmup");
assert_eq!(report.warmed, 1);
assert_eq!(report.skipped, 0);
let artifact_path = config
.disk_root(profile.id())
.join("artifacts/sha256_warmup-precompile.cwasm");
assert!(
artifact_path.exists(),
"expected cwasm at {artifact_path:?}"
);
let report2 = cache
.warmup(
&engine,
&[crate::cache::WarmupItem { key, bytes }],
crate::cache::WarmupMode::Strict,
)
.await
.expect("second warmup");
assert_eq!(report2.warmed, 0);
assert_eq!(report2.skipped, 1);
}