use std::path::PathBuf;
use std::sync::Arc;
use tempfile::TempDir;
use tensor_wasm_core::types::TenantId;
use tensor_wasm_jit::cache::{
CacheKey, CachedKernel, CompiledHandle, DiskCacheConfig, KernelCache,
};
use tensor_wasm_jit::ptx_emit::EmittedPtx;
fn fixture_ptx(text: &str) -> Arc<EmittedPtx> {
Arc::new(EmittedPtx {
text: text.to_string(),
launch_geometry: (1, 1),
})
}
#[test]
fn put_rejects_kernel_with_mismatched_integrity_hash() {
let cache = KernelCache::new();
let key = CacheKey::for_tenant(TenantId(1), 0xDEAD_BEEF, 80);
let bad = CachedKernel {
fingerprint: 0xDEAD_BEEF,
ptx: fixture_ptx(".visible .entry safe(){}"),
compiled: CompiledHandle::default(),
integrity_hash: [0xAA; 32],
};
cache.put(key, bad);
assert!(
cache.get(&key).is_none(),
"put must drop entries with a bad integrity hash"
);
}
#[test]
fn get_returns_kernel_built_via_constructor() {
let cache = KernelCache::new();
let key = CacheKey::for_tenant(TenantId(1), 0xBEEF, 80);
let kernel = CachedKernel::new(
0xBEEF,
fixture_ptx(".visible .entry func0(){}"),
CompiledHandle::default(),
);
cache.put(key, kernel);
let got = cache
.get(&key)
.expect("constructor-built kernel must round-trip");
assert_eq!(got.fingerprint, 0xBEEF);
assert!(got.verify_integrity());
}
#[test]
fn disk_cache_round_trip_with_correct_hmac() {
let tmp = TempDir::new().expect("tempdir");
let dir: PathBuf = tmp.path().to_path_buf();
let key_bytes = [0x42; 32];
let cache = KernelCache::new().with_disk_persistence(DiskCacheConfig {
dir: dir.clone(),
hmac_key: key_bytes,
});
let key = CacheKey::for_tenant(TenantId(7), 0xCAFE_BABE, 80);
let kernel = CachedKernel::new(
0xCAFE_BABE,
fixture_ptx(".visible .entry round_trip(){}"),
CompiledHandle::default(),
);
cache.put(key, kernel);
let cache2 = KernelCache::new().with_disk_persistence(DiskCacheConfig {
dir: dir.clone(),
hmac_key: key_bytes,
});
let got = cache2
.get(&key)
.expect("disk round-trip with correct HMAC must succeed");
assert_eq!(got.fingerprint, 0xCAFE_BABE);
assert!(got.ptx.text.contains("round_trip"));
}
#[test]
fn disk_cache_rejects_wrong_hmac_key_treated_as_miss() {
let tmp = TempDir::new().expect("tempdir");
let dir: PathBuf = tmp.path().to_path_buf();
let cache = KernelCache::new().with_disk_persistence(DiskCacheConfig {
dir: dir.clone(),
hmac_key: [0xAA; 32],
});
let key = CacheKey::for_tenant(TenantId(7), 0xCAFE_BABE, 80);
cache.put(
key,
CachedKernel::new(
0xCAFE_BABE,
fixture_ptx(".visible .entry wrong_key(){}"),
CompiledHandle::default(),
),
);
let cache_with_wrong_key = KernelCache::new().with_disk_persistence(DiskCacheConfig {
dir,
hmac_key: [0xBB; 32],
});
assert!(
cache_with_wrong_key.get(&key).is_none(),
"wrong HMAC key must treat the on-disk entry as a miss"
);
}
#[test]
fn disk_cache_rejects_tampered_payload() {
let tmp = TempDir::new().expect("tempdir");
let dir: PathBuf = tmp.path().to_path_buf();
let key_bytes = [0x33; 32];
let cache = KernelCache::new().with_disk_persistence(DiskCacheConfig {
dir: dir.clone(),
hmac_key: key_bytes,
});
let key = CacheKey::for_tenant(TenantId(7), 0xDEAD_F00D, 80);
cache.put(
key,
CachedKernel::new(
0xDEAD_F00D,
fixture_ptx(".visible .entry victim(){}"),
CompiledHandle::default(),
),
);
const ARTIFACT_HEADER_LEN: usize = 16 + 4 + 32; const ARTIFACT_HMAC_LEN: usize = 32;
let entries: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|ext| ext == "bin"))
.collect();
assert_eq!(
entries.len(),
1,
"T30 disk cache must have written exactly one artifact-store blob"
);
let path = entries[0].path();
let mut bytes = std::fs::read(&path).expect("read blob");
assert!(
bytes.len() > ARTIFACT_HEADER_LEN + ARTIFACT_HMAC_LEN,
"blob should have a non-empty zstd body between header and tag"
);
let flip_at = ARTIFACT_HEADER_LEN + (bytes.len() - ARTIFACT_HEADER_LEN - ARTIFACT_HMAC_LEN) / 2;
bytes[flip_at] ^= 0xFF;
std::fs::write(&path, &bytes).expect("rewrite tampered blob");
let cache2 = KernelCache::new().with_disk_persistence(DiskCacheConfig {
dir,
hmac_key: key_bytes,
});
assert!(
cache2.get(&key).is_none(),
"tampered on-disk entry must be rejected as a miss"
);
}