sim_lib_stream_host/
cookbook.rs1use std::sync::Arc;
4
5use sim_kernel::{Cx, DefaultFactory, EagerPolicy, Expr, HandleSeed, NumberLiteral, Symbol};
6
7use crate::{FakeBackend, HostBackend, HostBackendRegistry, stream_host_capability};
8
9pub fn fake_backend_demo() -> Expr {
11 let backend = FakeBackend::new();
12 let inventory = backend.enumerate().expect("fake backend enumerates");
13 let mut registry = HostBackendRegistry::new();
14 registry.register(backend).expect("fake backend registers");
15 let mut cx = authorized_demo_cx();
16 let opened = registry
17 .open_checked(
18 &mut cx,
19 FakeBackend::data_request(8).expect("valid fake data request"),
20 )
21 .expect("fake backend opens data stream");
22 let config = opened.config();
23
24 Expr::Map(vec![
25 (field("kind"), sym("stream-host", "fake-backend")),
26 (field("backend"), Expr::Symbol(config.backend().clone())),
27 (field("device"), Expr::Symbol(config.device().clone())),
28 (field("media"), Expr::Symbol(config.media().symbol())),
29 (field("clock"), Expr::Symbol(config.clock().clock().clone())),
30 (field("devices"), number(inventory.devices().len())),
31 (field("ports"), number(inventory.ports().len())),
32 (field("callback-queue"), sym("stream-host", "bounded")),
33 (field("hardware-required"), Expr::Bool(false)),
34 ])
35}
36
37fn field(name: &str) -> Expr {
38 Expr::Symbol(Symbol::qualified("stream-host", name))
39}
40
41fn sym(namespace: &str, name: &str) -> Expr {
42 Expr::Symbol(Symbol::qualified(namespace, name))
43}
44
45fn number(value: impl ToString) -> Expr {
46 Expr::Number(NumberLiteral {
47 domain: Symbol::qualified("numbers", "i64"),
48 canonical: value.to_string(),
49 })
50}
51
52#[allow(
53 clippy::unit_arg,
54 reason = "published sim-kernel grants return unit; workspace grants return Result"
55)]
56fn authorized_demo_cx() -> Cx {
57 let (mut cx, seat) = Cx::new_seated(
58 Arc::new(EagerPolicy),
59 Arc::new(DefaultFactory),
60 HandleSeed::new(1),
61 );
62 seat.grant(&mut cx, stream_host_capability())
63 .assert_demo_granted();
64 cx
65}
66
67trait DemoGrantResult {
68 fn assert_demo_granted(self);
69}
70
71impl DemoGrantResult for () {
72 fn assert_demo_granted(self) {}
73}
74
75impl<E: std::fmt::Display> DemoGrantResult for std::result::Result<(), E> {
76 fn assert_demo_granted(self) {
77 if let Err(err) = self {
78 panic!("demo host grant is valid: {err}");
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use sim_codec::encode_with_codec;
87 use sim_codec_lisp::LispCodecLib;
88 use sim_lib_stream_core::StreamMedia;
89
90 #[test]
91 fn fake_backend_demo_opens_modeled_data_stream() {
92 let Expr::Map(entries) = fake_backend_demo() else {
93 panic!("fake backend demo is a map")
94 };
95 assert!(entries.iter().any(|(_, value)| {
96 matches!(value, Expr::Symbol(symbol) if symbol.as_qualified_str() == "stream/media/data")
97 }));
98 assert!(entries.iter().any(|(_, value)| *value == Expr::Bool(false)));
99 let mut registry = HostBackendRegistry::new();
100 registry.register(FakeBackend::new()).expect("register");
101 let mut cx = authorized_demo_cx();
102 let opened = registry
103 .open_checked(&mut cx, FakeBackend::data_request(4).expect("request"))
104 .expect("open");
105 assert_eq!(opened.config().media(), StreamMedia::Data);
106 }
107
108 #[test]
109 fn fake_backend_recipe_expectation_matches_demo_output() {
110 let mut cx = authorized_demo_cx();
111 cx.load_lib(&sim_lib_numbers_i64::I64NumbersLib::new())
112 .expect("load i64 numbers");
113 let lisp = Symbol::qualified("codec", "lisp");
114 let codec = LispCodecLib::new(cx.registry_mut().fresh_codec_id()).expect("lisp codec");
115 cx.load_lib(&codec).expect("load lisp codec");
116
117 let rendered = encode_with_codec(&mut cx, &lisp, &fake_backend_demo(), Default::default())
118 .expect("encode fake backend demo")
119 .into_text()
120 .expect("lisp codec emits text");
121 assert_eq!(recipe_expected_result(), rendered);
122 }
123
124 fn recipe_expected_result() -> String {
125 let recipe = include_str!("../recipes/01-basics/fake-backend/recipe.toml");
126 let result_line = recipe
127 .lines()
128 .find(|line| line.starts_with("result = "))
129 .expect("recipe result line");
130 result_line
131 .strip_prefix("result = \"")
132 .and_then(|line| line.strip_suffix('"'))
133 .expect("quoted recipe result")
134 .replace("\\\"", "\"")
135 }
136}