sim-lib-stream-host 0.1.4

Host-device stream backend substrate for SIM streams.
Documentation
use sim_kernel::{Expr, Symbol};

use crate::{
    BoundedContentStore, ContentFrame, DeviceCapability, DeviceError, DevicePlacement,
    DeviceProvider, DeviceSession, DeviceSite, PlacementError, RetentionWindow, StoreKey,
    StubProvider, retention_reason, size_bound_reason,
};

#[test]
fn device_stub_unsupported_and_placement_guard() {
    let profile = crate::DeviceProfile::modeled_edge();
    let provider = StubProvider::new(profile.clone());
    assert_eq!(provider.profile(), &profile);
    assert!(matches!(provider.open(), Err(DeviceError::Unsupported)));

    let mut session = provider.session();
    assert_eq!(session.profile(), provider.profile());
    assert!(matches!(session.start(), Err(DeviceError::Unsupported)));
    assert!(matches!(
        session.poll("device-caps"),
        Err(DeviceError::Unsupported)
    ));
    assert!(matches!(
        session.send(&Expr::Symbol(Symbol::qualified("device/command", "haptic"))),
        Err(DeviceError::Unsupported)
    ));
    session.stop().unwrap();

    let codec = Symbol::qualified("codec", "lisp");
    let encoder = DeviceSite::edge_local(
        Symbol::qualified("device/site", "encoder"),
        provider.profile().clone(),
        codec.clone(),
    );
    let adapter = DeviceSite::remote(
        Symbol::qualified("device/site", "adapter"),
        provider.profile().clone(),
        codec,
    );
    let placement = DevicePlacement::new(encoder, adapter);

    assert_eq!(
        placement.validate(),
        Err(PlacementError::AdapterMustBeEdgeLocal)
    );
}

#[test]
fn content_store_obeys_size_bound_and_retention_reaper() {
    assert_eq!(
        DeviceCapability::Pose.capability_name().as_str(),
        "device/pose"
    );
    assert_eq!(
        DeviceCapability::Pose.grant_symbol(),
        Symbol::qualified("device", "pose")
    );

    let session = Symbol::qualified("device/session", "primary");
    let key_a = StoreKey::named("a");
    let key_b = StoreKey::named("b");
    let key_c = StoreKey::named("c");
    let mut store = BoundedContentStore::new(6).unwrap();

    let evicted = store
        .insert(ContentFrame::new(
            key_a.clone(),
            session.clone(),
            1,
            0,
            4,
            Expr::String("first".to_owned()),
        ))
        .unwrap();
    assert!(evicted.is_empty());

    let evicted = store
        .insert(ContentFrame::new(
            key_b.clone(),
            session.clone(),
            1,
            0,
            4,
            Expr::String("second".to_owned()),
        ))
        .unwrap();
    assert_eq!(evicted.len(), 1);
    assert_eq!(evicted[0].key, key_a);
    assert_eq!(evicted[0].reason, size_bound_reason());
    assert!(!store.contains(&key_a));
    assert!(store.contains(&key_b));

    store
        .insert(ContentFrame::new(
            key_c.clone(),
            session.clone(),
            2,
            0,
            2,
            Expr::String("third".to_owned()),
        ))
        .unwrap();
    let evicted = store.sweep_retention(2, 1, &[RetentionWindow::new(session.clone(), 1, 1_000)]);
    assert_eq!(evicted.len(), 2);
    assert!(evicted.iter().any(|item| item.key == key_b));
    assert!(evicted.iter().any(|item| item.key == key_c));
    assert!(evicted.iter().all(|item| item.reason == retention_reason()));
    assert!(store.is_empty());
}