hara-native 0.1.13

HAL-free native host runtime and package launcher for Hara
Documentation
use std::cell::RefCell;
use std::collections::BTreeSet;
use std::rc::Rc;

use crate::instrumentation::{
    Capability, EventDelivery, EventKind, InstrumentFilter, InstrumentMode, InstrumentRegistration,
    InstrumentationError, InstrumentationHub, ProjectionRequest, RuntimeBackend, TargetDescriptor,
    TargetKind,
};
use crate::live_session::{LiveSessionCommand, LiveSessionRequest, LiveSessionStatus, LiveSource};
use crate::{SessionId, SessionKernel};

use super::*;

fn set<T: Ord>(values: impl IntoIterator<Item = T>) -> BTreeSet<T> {
    values.into_iter().collect()
}

fn target(
    id: &str,
    session_id: &str,
    kind: TargetKind,
    capabilities: impl IntoIterator<Item = Capability>,
) -> TargetDescriptor {
    TargetDescriptor {
        target_id: id.into(),
        session_id: session_id.into(),
        kind,
        backend: RuntimeBackend::new("rust").expect("test backend is valid"),
        capabilities: set(capabilities),
    }
}

fn lifecycle_registration(
    id: &str,
    session_id: &str,
    target_id: &str,
    kind: TargetKind,
) -> InstrumentRegistration {
    InstrumentRegistration {
        instrument_id: id.into(),
        session_id: session_id.into(),
        mode: InstrumentMode::Passive,
        capabilities: set([Capability::EventLifecycle]),
        events: set([EventKind::ExecutionTerminal]),
        filter: InstrumentFilter {
            session_id: Some(session_id.into()),
            target_ids: set([target_id.into()]),
            target_kinds: set([kind]),
            backends: set([RuntimeBackend::new("rust").expect("Rust is a valid backend id")]),
        },
        projection: ProjectionRequest::default(),
        delivery: EventDelivery::Queue { capacity: 16 },
    }
}

fn source(id: &str, revision: &str) -> LiveSource {
    LiveSource::new(id, revision, "(+ 19 23)").expect("test source is valid")
}

fn target_identity(payload: &serde_json::Value) -> (String, u64) {
    let target = payload
        .get("target")
        .and_then(serde_json::Value::as_object)
        .expect("live-session snapshot exposes a bounded target identity");
    let id = target
        .get("id")
        .and_then(serde_json::Value::as_str)
        .expect("target id is text")
        .to_owned();
    let generation = target
        .get("generation")
        .and_then(serde_json::Value::as_u64)
        .expect("target generation is an unsigned integer");
    (id, generation)
}

#[test]
fn trusted_native_target_identity_is_generation_fenced() {
    let hub = Rc::new(RefCell::new(InstrumentationHub::new()));
    let service = NativeInstrumentation::new("session", hub.clone());
    let original = hub
        .borrow_mut()
        .register_target(target(
            "execution",
            "session",
            TargetKind::Interpreter,
            [Capability::EventLifecycle],
        ))
        .expect("original target registration");
    hub.borrow_mut()
        .remove_target(&original)
        .expect("remove original target");
    let replacement = hub
        .borrow_mut()
        .register_target(target(
            "execution",
            "session",
            TargetKind::Interpreter,
            [Capability::EventLifecycle],
        ))
        .expect("replacement target registration");

    assert_eq!(original.generation(), 0);
    assert_eq!(replacement.generation(), 1);
    assert!(matches!(
        service.bind_target_identity("execution", original.generation()),
        Err(NativeInstrumentationError::Hub(
            InstrumentationError::StaleTargetHandle {
                target_id,
                generation: 0,
            }
        )) if target_id == "execution"
    ));
    let bound = service
        .bind_target_identity("execution", replacement.generation())
        .expect("current target identity binds");
    assert_eq!(bound.target_id(), "execution");
    assert_eq!(bound.generation(), 1);
}

#[test]
fn trusted_native_binds_and_observes_real_interpreter_live_target() {
    let mut kernel = SessionKernel::new();
    let session_id = SessionId::parse("trusted-interpreter").unwrap();
    kernel.create_session(session_id.clone()).unwrap();
    let service = kernel
        .instrumentation(&session_id)
        .expect("trusted host service");
    let started = kernel
        .session_mut(&session_id)
        .unwrap()
        .start_interpreter_live_session(
            "debug-session",
            source("interpreter.hal", "sha256:interpreter"),
        )
        .expect("interpreter live session starts");
    let snapshot = kernel
        .session_mut(&session_id)
        .unwrap()
        .dispatch_live_session(LiveSessionRequest::for_state(
            "snapshot-target",
            &started,
            LiveSessionCommand::Snapshot,
        ))
        .expect("snapshot exposes target identity");
    let (target_id, generation) = target_identity(&snapshot.payload);
    let target = service
        .bind_target_identity(target_id.clone(), generation)
        .expect("trusted host binds the real interpreter target");
    let descriptor = service
        .target_descriptor(&target)
        .expect("bounded target descriptor");
    assert_eq!(descriptor.target_id, target_id);
    assert_eq!(descriptor.session_id, session_id.as_str());
    assert_eq!(descriptor.kind, TargetKind::Interpreter);

    let instrument = service
        .register(lifecycle_registration(
            "host/interpreter-terminal",
            session_id.as_str(),
            target.target_id(),
            TargetKind::Interpreter,
        ))
        .expect("passive host instrument registration");
    service
        .attach(&instrument, &target)
        .expect("passive host attaches to interpreter target");

    let run = kernel
        .session_mut(&session_id)
        .unwrap()
        .dispatch_live_session(LiveSessionRequest::for_state(
            "run-interpreter",
            &snapshot.state,
            LiveSessionCommand::Run {
                boundary_limit: 1_000,
            },
        ))
        .expect("interpreter live target runs");
    assert_eq!(run.state.status, LiveSessionStatus::Returned);
    let batch = service
        .drain_events(&instrument)
        .expect("passive host drains its own queue");
    assert!(batch.events.iter().any(|event| {
        event.envelope.event == EventKind::ExecutionTerminal
            && event.envelope.target_id == target.target_id()
            && event.envelope.session_id == session_id.as_str()
            && event.envelope.target_kind == TargetKind::Interpreter
    }));
}

#[cfg(all(feature = "bytecode-observation", feature = "bytecode-instrumentation"))]
#[test]
fn trusted_native_binds_and_observes_real_hbc_live_target() {
    let mut kernel = SessionKernel::new();
    let session_id = SessionId::parse("trusted-hbc").unwrap();
    kernel.create_session(session_id.clone()).unwrap();
    let service = kernel
        .instrumentation(&session_id)
        .expect("trusted host service");
    let started = kernel
        .session_mut(&session_id)
        .unwrap()
        .start_hbc_live_session("debug-session", source("hbc.hal", "sha256:hbc"))
        .expect("HBC live session starts");
    let snapshot = kernel
        .session_mut(&session_id)
        .unwrap()
        .dispatch_live_session(LiveSessionRequest::for_state(
            "snapshot-target",
            &started,
            LiveSessionCommand::Snapshot,
        ))
        .expect("snapshot exposes target identity");
    let (target_id, generation) = target_identity(&snapshot.payload);
    let target = service
        .bind_target_identity(target_id.clone(), generation)
        .expect("trusted host binds the real HBC target");
    let descriptor = service
        .target_descriptor(&target)
        .expect("bounded target descriptor");
    assert_eq!(descriptor.target_id, target_id);
    assert_eq!(descriptor.session_id, session_id.as_str());
    assert_eq!(descriptor.kind, TargetKind::Hbc);

    let instrument = service
        .register(lifecycle_registration(
            "host/hbc-terminal",
            session_id.as_str(),
            target.target_id(),
            TargetKind::Hbc,
        ))
        .expect("passive host instrument registration");
    service
        .attach(&instrument, &target)
        .expect("passive host attaches to HBC target");

    let run = kernel
        .session_mut(&session_id)
        .unwrap()
        .dispatch_live_session(LiveSessionRequest::for_state(
            "run-hbc",
            &snapshot.state,
            LiveSessionCommand::Run {
                boundary_limit: 10_000,
            },
        ))
        .expect("HBC live target runs");
    assert_eq!(run.state.status, LiveSessionStatus::Returned);
    let batch = service
        .drain_events(&instrument)
        .expect("passive host drains its own queue");
    assert!(batch.events.iter().any(|event| {
        event.envelope.event == EventKind::ExecutionTerminal
            && event.envelope.target_id == target.target_id()
            && event.envelope.session_id == session_id.as_str()
            && event.envelope.target_kind == TargetKind::Hbc
    }));
}