greentic-runner-host 0.5.10

Host runtime shim for Greentic runner: config, pack loading, activity handling
Documentation
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 }],
            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);
}