sim-kernel 0.1.4

Small protocol kernel contracts for the expandable SIM Rust runtime.
Documentation
use std::sync::Arc;

use crate::{
    CapabilityName, Consistency, Cx, Diagnostic, Error, EvalFabric, EvalMode, EvalReply,
    EvalRequest, EventKind, EventSource, Expr, MatchScore, ObserveMode, RealizeRequest, Result,
    Shape, ShapeDoc, ShapeMatch, Value, realize_events, realize_final,
};

#[derive(Clone)]
struct StaticFabric {
    reply: EvalReply,
}

impl EvalFabric for StaticFabric {
    fn realize(&self, _cx: &mut Cx, _request: EvalRequest) -> Result<EvalReply> {
        Ok(self.reply.clone())
    }
}

struct FixedShape {
    accepted: bool,
}

impl Shape for FixedShape {
    fn check_value(&self, _cx: &mut Cx, _value: Value) -> Result<ShapeMatch> {
        if self.accepted {
            Ok(ShapeMatch::accept(MatchScore::exact(1)))
        } else {
            Ok(ShapeMatch::reject("realize result rejected"))
        }
    }

    fn check_expr(&self, _cx: &mut Cx, _expr: &Expr) -> Result<ShapeMatch> {
        Ok(ShapeMatch::reject("value-only shape"))
    }

    fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
        Ok(ShapeDoc::new("fixed"))
    }
}

fn request(expr: Expr) -> EvalRequest {
    EvalRequest {
        expr,
        result_shape: None,
        required_capabilities: Vec::new(),
        deadline: None,
        consistency: Consistency::LocalFirst,
        mode: EvalMode::Eval,
        answer_limit: None,
        stream_buffer: None,
        stream: false,
        trace: false,
    }
}

fn reply(value: Value) -> EvalReply {
    EvalReply {
        value,
        diagnostics: Vec::new(),
        trace: None,
    }
}

fn shape_value(cx: &mut Cx, accepted: bool) -> Value {
    cx.factory()
        .opaque(Arc::new(FixedShape { accepted }))
        .unwrap()
}

#[test]
fn eventful_realize_source_drains_to_final_then_done() {
    let mut cx = Cx::stub();
    let value = cx.factory().string("ok".to_owned()).unwrap();
    let fabric = StaticFabric {
        reply: reply(value.clone()),
    };

    let source = realize_events(&mut cx, &fabric, request(Expr::String("task".to_owned())))
        .expect("event source");
    let mut kinds = Vec::new();
    while let Some(event) = source.next(&mut cx).unwrap() {
        kinds.push(event.kind);
    }

    assert!(matches!(kinds.first(), Some(EventKind::Started { .. })));
    assert!(matches!(
        kinds.get(kinds.len().saturating_sub(2)),
        Some(EventKind::Final(_))
    ));
    assert!(matches!(kinds.last(), Some(EventKind::Done)));
}

#[test]
fn eventful_realize_drains_back_to_eval_reply() {
    let mut cx = Cx::stub();
    let value = cx.factory().string("final".to_owned()).unwrap();
    let fabric = StaticFabric {
        reply: reply(value.clone()),
    };

    let drained = realize_final(&mut cx, &fabric, request(Expr::Nil)).unwrap();

    assert_eq!(drained.value, value);
    assert!(drained.diagnostics.is_empty());
    assert!(drained.trace.is_none());
}

#[test]
fn eventful_realize_accepts_matching_result_shape() {
    let mut cx = Cx::stub();
    let value = cx.factory().string("final".to_owned()).unwrap();
    let mut request = request(Expr::Nil);
    request.result_shape = Some(shape_value(&mut cx, true));
    let fabric = StaticFabric {
        reply: reply(value.clone()),
    };

    let drained = realize_final(&mut cx, &fabric, request).unwrap();

    assert_eq!(drained.value, value);
}

#[test]
fn eventful_realize_rejects_mismatched_result_shape() {
    let mut cx = Cx::stub();
    let value = cx.factory().string("final".to_owned()).unwrap();
    let mut request = request(Expr::Nil);
    request.result_shape = Some(shape_value(&mut cx, false));
    let fabric = StaticFabric {
        reply: reply(value),
    };

    let err = realize_events(&mut cx, &fabric, request).unwrap_err();

    assert!(matches!(err, Error::WrongShape { .. }));
}

#[test]
fn diagnostics_survive_eventful_realize() {
    let mut cx = Cx::stub();
    let value = cx.factory().nil().unwrap();
    let diagnostic = Diagnostic::info("kept");
    let fabric = StaticFabric {
        reply: EvalReply {
            value,
            diagnostics: vec![diagnostic.clone()],
            trace: None,
        },
    };

    let drained = realize_final(&mut cx, &fabric, request(Expr::Nil)).unwrap();

    assert_eq!(drained.diagnostics, vec![diagnostic]);
}

#[test]
fn trace_survives_eventful_realize() {
    let mut cx = Cx::stub();
    let value = cx.factory().nil().unwrap();
    let trace = cx.factory().string("trace".to_owned()).unwrap();
    let fabric = StaticFabric {
        reply: EvalReply {
            value,
            diagnostics: Vec::new(),
            trace: Some(trace.clone()),
        },
    };

    let drained = realize_final(&mut cx, &fabric, request(Expr::Nil)).unwrap();

    assert_eq!(drained.trace, Some(trace));
}

#[test]
fn old_eval_request_observation_flags_map_to_realize_request() {
    let mut cx = Cx::stub();
    let mut streaming = request(Expr::Nil);
    streaming.stream = true;
    streaming.trace = true;
    streaming.stream_buffer = Some(8);
    streaming.required_capabilities = vec![CapabilityName::new("demo")];

    let eventful = RealizeRequest::from_eval_request(&mut cx, &streaming).unwrap();
    let back = eventful.to_eval_request(&mut cx).unwrap();

    assert_eq!(eventful.observe, ObserveMode::Events);
    assert_eq!(eventful.buffer_limit, Some(8));
    assert_eq!(
        eventful.required_capabilities,
        vec![CapabilityName::new("demo")]
    );
    assert!(back.stream);
    assert!(back.trace);
    assert_eq!(back.stream_buffer, Some(8));
}