sipp-rs 0.1.0

Unified Rust library for extensible Sipp inference
//! Tests the `lifecycle::backend_policy` module in `sipp`.
//!
//! Covers lifecycle registry, storage, browser, service, and pairing behavior with temporary storage and pure fixtures instead of native runtime loading.

use super::*;
use crate::lifecycle::test_support::strings;
use crate::runtime::config::SplitMode;

fn caps(compiled: &[&str], available: &[&str]) -> BackendCapabilities {
    BackendCapabilities {
        compiled: strings(compiled),
        available: strings(available),
        gpu_offload_supported: compiled.iter().any(|value| *value != "cpu"),
    }
}

#[test]
fn cpu_forces_gpu_layers_zero() {
    let options = ModelLoadOptions {
        backend: BackendPreference::Cpu,
        stats: StatsMode::Off,
        ..ModelLoadOptions::default()
    };

    let plan =
        BackendPolicy::select_with_capabilities(&options, &caps(&[], &["cpu"])).expect("backend");

    assert_eq!(plan.selection.selected, "cpu");
    assert_eq!(plan.config.placement.gpu_layers, GpuLayerConfig::Count(0));
    assert_eq!(plan.config.placement.split_mode, SplitMode::Layer);
    assert_eq!(
        plan.config.context.flash_attention,
        FlashAttentionMode::Disabled
    );
    assert!(!plan.config.context.offload_kqv);
    assert!(!plan.config.context.op_offload);
    assert!(!plan.config.observability.runtime_metrics);
    assert!(!plan.config.observability.backend_profiling);
}

#[test]
fn cuda_requires_compiled_available_backend() {
    let options = ModelLoadOptions {
        backend: BackendPreference::Cuda,
        ..ModelLoadOptions::default()
    };

    let error = BackendPolicy::select_with_capabilities(&options, &caps(&[], &["cpu"]))
        .expect_err("missing cuda");

    assert!(matches!(error, ModelError::InvalidModelSource(_)));
}

#[test]
fn cuda_selects_full_offload_by_default() {
    let options = ModelLoadOptions {
        backend: BackendPreference::Cuda,
        stats: StatsMode::Profile,
        ..ModelLoadOptions::default()
    };

    let plan =
        BackendPolicy::select_with_capabilities(&options, &caps(&["cuda"], &["cuda", "cpu"]))
            .expect("cuda");

    assert_eq!(plan.selection.selected, "cuda");
    assert_eq!(plan.config.placement.gpu_layers, GpuLayerConfig::Auto);
    assert!(plan.config.observability.runtime_metrics);
    assert!(plan.config.observability.backend_profiling);
}

#[test]
fn webgpu_selects_full_offload_by_default() {
    let options = ModelLoadOptions {
        backend: BackendPreference::WebGpu,
        ..ModelLoadOptions::default()
    };

    let plan =
        BackendPolicy::select_with_capabilities(&options, &caps(&["webgpu"], &["cpu", "webgpu"]))
            .expect("webgpu");

    assert_eq!(plan.selection.selected, "webgpu");
    assert_eq!(plan.config.placement.gpu_layers, GpuLayerConfig::Auto);
    assert!(plan.selection.gpu_offload_expected);
}

#[test]
fn auto_prefers_accelerator_then_cpu() {
    let plan = BackendPolicy::select_with_capabilities(
        &ModelLoadOptions::default(),
        &caps(&["vulkan"], &["cpu", "vulkan"]),
    )
    .expect("auto");
    assert_eq!(plan.selection.selected, "vulkan");

    let plan =
        BackendPolicy::select_with_capabilities(&ModelLoadOptions::default(), &caps(&[], &[]))
            .expect("auto cpu");
    assert_eq!(plan.selection.selected, "cpu");
    assert_eq!(plan.config.placement.gpu_layers, GpuLayerConfig::Count(0));
    assert_eq!(plan.config.placement.split_mode, SplitMode::Layer);
    assert!(!plan.config.context.offload_kqv);
}

#[test]
fn select_with_capabilities_normalizes_backend_names() {
    let plan = BackendPolicy::select_with_capabilities(
        &ModelLoadOptions {
            backend: BackendPreference::Cuda,
            ..ModelLoadOptions::default()
        },
        &BackendCapabilities {
            compiled: strings(&["CUDA backend", "cuda"]),
            available: strings(&["NVIDIA CUDA", "CPU"]),
            gpu_offload_supported: true,
        },
    )
    .expect("cuda");

    assert_eq!(plan.selection.selected, "cuda");
    assert_eq!(plan.selection.available, vec!["cpu", "cuda"]);
}

#[test]
fn normalize_backend_names_drops_empty_and_deduplicates() {
    let names = strings(&[" CUDA backend ", "cuda", " ", "CPU"]);

    assert_eq!(normalize_backend_names(&names), vec!["cpu", "cuda"]);
    assert_eq!(normalize_backend_names_or_cpu(&[]), vec!["cpu"]);
}