trf_codegen_runtime/
lib.rs1use anyhow::Context as _;
10use fidl::endpoints::DiscoverableProtocolMarker;
11use fidl_fuchsia_io as fio;
12use fidl_fuchsia_testing_harness::RealmProxy_Proxy;
13use fidl_fuchsia_trf_factory::{ConfigOverride, CreateRealmRequest, FactoryMarker};
14use futures::channel::mpsc;
15use futures::stream::Stream;
16use std::pin::Pin;
17use std::sync::Mutex;
18use std::task::{Context, Poll};
19
20pub trait MockControlMarker {
22 type ControlProxy;
24
25 fn connect_from_namespace() -> Result<Self::ControlProxy, anyhow::Error>;
27}
28
29impl<P> MockControlMarker for P
30where
31 P: DiscoverableProtocolMarker,
32{
33 type ControlProxy = P::Proxy;
34
35 fn connect_from_namespace() -> Result<Self::ControlProxy, anyhow::Error> {
36 fuchsia_component::client::connect_to_protocol::<P>()
37 .context("failed to connect to mock control proxy from namespace")
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum LifecycleEvent {
44 Started(String),
45 Stopped(String),
46}
47
48pub struct LifecycleStream {
50 receiver: mpsc::UnboundedReceiver<LifecycleEvent>,
51}
52
53impl LifecycleStream {
54 pub fn new(receiver: mpsc::UnboundedReceiver<LifecycleEvent>) -> Self {
56 Self { receiver }
57 }
58
59 pub async fn next_event(&mut self) -> Option<LifecycleEvent> {
61 use futures::StreamExt as _;
62 self.receiver.next().await
63 }
64}
65
66impl Stream for LifecycleStream {
67 type Item = LifecycleEvent;
68
69 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
70 Pin::new(&mut self.receiver).poll_next(cx)
71 }
72}
73
74pub struct TestRealm {
76 realm_proxy: Option<RealmProxy_Proxy>,
77 lifecycle_senders: Mutex<Vec<mpsc::UnboundedSender<LifecycleEvent>>>,
78}
79
80impl Default for TestRealm {
81 fn default() -> Self {
82 Self { realm_proxy: None, lifecycle_senders: Mutex::new(Vec::new()) }
83 }
84}
85
86impl TestRealm {
87 pub async fn create(overrides: Vec<ConfigOverride>) -> Result<Self, anyhow::Error> {
89 let factory = fuchsia_component::client::connect_to_protocol::<FactoryMarker>()
90 .context("failed to connect to Factory")?;
91 let proxy_client_end = factory
92 .create_realm(CreateRealmRequest { overrides: Some(overrides), ..Default::default() })
93 .await?
94 .map_err(|e| anyhow::format_err!("Factory error: {:?}", e))?;
95 let proxy = proxy_client_end.into_proxy();
96 Ok(Self { realm_proxy: Some(proxy), lifecycle_senders: Mutex::new(Vec::new()) })
97 }
98
99 pub async fn connect_to_protocol<P: DiscoverableProtocolMarker>(
101 &self,
102 ) -> Result<P::Proxy, anyhow::Error> {
103 if let Some(proxy) = &self.realm_proxy {
104 let (client_end, server_end) = fidl::endpoints::create_proxy::<P>();
105 proxy
106 .connect_to_named_protocol(P::PROTOCOL_NAME, server_end.into_channel())
107 .await
108 .context("failed to send connect_to_named_protocol request")?
109 .map_err(|e| anyhow::anyhow!("OperationError: {:?}", e))?;
110 Ok(client_end)
111 } else {
112 fuchsia_component::client::connect_to_protocol::<P>()
113 .context("failed to connect to protocol from incoming namespace")
114 }
115 }
116
117 pub fn get_control<M: MockControlMarker>(&self) -> Result<M::ControlProxy, anyhow::Error> {
119 M::connect_from_namespace()
120 }
121
122 pub async fn get_directory(&self, path: &str) -> Result<fio::DirectoryProxy, anyhow::Error> {
124 let canonical_path = fuchsia_fs::canonicalize_path(path);
125 if let Some(proxy) = &self.realm_proxy {
126 let (client_end, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
127 proxy
128 .open_service(canonical_path, server_end.into_channel())
129 .await
130 .context("failed to send open_service request")?
131 .map_err(|e| anyhow::anyhow!("OperationError: {:?}", e))?;
132 Ok(client_end)
133 } else {
134 let directory_proxy = fuchsia_fs::directory::open_in_namespace(
135 canonical_path,
136 fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_WRITABLE,
137 )
138 .context("failed to open directory in namespace")?;
139 Ok(directory_proxy)
140 }
141 }
142
143 pub fn subscribe_to_lifecycle(&self) -> Result<LifecycleStream, anyhow::Error> {
145 let (sender, receiver) = mpsc::unbounded();
146 self.lifecycle_senders.lock().unwrap().push(sender);
147 Ok(LifecycleStream::new(receiver))
148 }
149
150 pub fn emit_lifecycle_event(&self, event: LifecycleEvent) {
152 let mut senders = self.lifecycle_senders.lock().unwrap();
153 senders.retain(|sender| sender.unbounded_send(event.clone()).is_ok());
154 }
155
156 pub async fn stop_component(&self, name: &str) -> Result<(), anyhow::Error> {
157 let lifecycle =
158 self.connect_to_protocol::<fidl_fuchsia_sys2::LifecycleControllerMarker>().await?;
159 lifecycle
160 .stop_instance(&format!("./{}", name))
161 .await?
162 .map_err(|e| anyhow::format_err!("{:?}", e))?;
163 self.emit_lifecycle_event(LifecycleEvent::Stopped(name.to_string()));
164 Ok(())
165 }
166
167 pub async fn is_running(&self, name: &str) -> Result<bool, anyhow::Error> {
168 let query = self.connect_to_protocol::<fidl_fuchsia_sys2::RealmQueryMarker>().await?;
169 let info = query
170 .get_instance(&format!("./{}", name))
171 .await?
172 .map_err(|e| anyhow::format_err!("{:?}", e))?;
173 if let Some(resolved) = info.resolved_info {
174 Ok(resolved.execution_info.is_some())
175 } else {
176 Ok(false)
177 }
178 }
179
180 pub async fn start_component(&self, name: &str) -> Result<(), anyhow::Error> {
181 let lifecycle =
182 self.connect_to_protocol::<fidl_fuchsia_sys2::LifecycleControllerMarker>().await?;
183 let (_, binder) = fidl::endpoints::create_endpoints();
184 lifecycle
185 .start_instance(&format!("./{}", name), binder)
186 .await?
187 .map_err(|e| anyhow::format_err!("{:?}", e))?;
188 self.emit_lifecycle_event(LifecycleEvent::Started(name.to_string()));
189 Ok(())
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use fidl::endpoints::{ServerEnd, create_proxy_and_stream};
197 use fidl_fuchsia_sys2 as fsys2;
198 use fidl_fuchsia_testing_harness::{RealmProxy_Marker, RealmProxy_Request};
199 use fidl_fuchsia_trf_factory::FactoryMarker;
200 use futures::StreamExt;
201
202 #[fuchsia::test]
203 async fn test_lifecycle_stream_and_realm_emit() {
204 let realm = TestRealm::default();
205 let mut stream = realm.subscribe_to_lifecycle().unwrap();
206
207 realm.emit_lifecycle_event(LifecycleEvent::Started("hello".to_string()));
208 realm.emit_lifecycle_event(LifecycleEvent::Stopped("hello".to_string()));
209
210 assert_eq!(stream.next_event().await, Some(LifecycleEvent::Started("hello".to_string())));
211 assert_eq!(stream.next_event().await, Some(LifecycleEvent::Stopped("hello".to_string())));
212 }
213
214 #[fuchsia::test]
215 async fn test_create_failure_no_namespace() {
216 let res = TestRealm::create(vec![]).await;
217 assert!(res.is_err());
218 }
219
220 #[fuchsia::test]
221 async fn test_get_control_success_lazy() {
222 let realm = TestRealm::default();
223 let control = realm.get_control::<FactoryMarker>();
224 assert!(control.is_ok());
225 }
226
227 #[fuchsia::test]
228 async fn test_connect_to_protocol_no_proxy() {
229 let realm = TestRealm::default();
230 let res = realm.connect_to_protocol::<FactoryMarker>().await;
231 assert!(res.is_ok());
232 }
233
234 #[fuchsia::test]
235 async fn test_get_directory_no_proxy() {
236 let realm = TestRealm::default();
237 let dir = realm.get_directory("/pkg").await;
238 assert!(dir.is_err()); }
240
241 #[fuchsia::test]
242 async fn test_connect_to_protocol_with_proxy() {
243 let (proxy, mut stream) = create_proxy_and_stream::<RealmProxy_Marker>();
244 let realm =
245 TestRealm { realm_proxy: Some(proxy), lifecycle_senders: Mutex::new(Vec::new()) };
246
247 let task = fuchsia_async::Task::local(async move {
248 if let Some(Ok(RealmProxy_Request::ConnectToNamedProtocol {
249 protocol,
250 responder,
251 ..
252 })) = stream.next().await
253 {
254 assert_eq!(protocol, fsys2::LifecycleControllerMarker::PROTOCOL_NAME);
255 let _ = responder.send(Ok(()));
256 }
257 });
258
259 let res = realm.connect_to_protocol::<fsys2::LifecycleControllerMarker>().await;
260 assert!(res.is_ok());
261 task.await;
262 }
263
264 #[fuchsia::test]
265 async fn test_get_directory_with_proxy() {
266 let (proxy, mut stream) = create_proxy_and_stream::<RealmProxy_Marker>();
267 let realm =
268 TestRealm { realm_proxy: Some(proxy), lifecycle_senders: Mutex::new(Vec::new()) };
269
270 let task = fuchsia_async::Task::local(async move {
271 if let Some(Ok(RealmProxy_Request::OpenService { responder, .. })) = stream.next().await
272 {
273 let _ = responder.send(Ok(()));
274 }
275 });
276
277 let res = realm.get_directory("/some/path").await;
278 assert!(res.is_ok());
279 task.await;
280 }
281
282 #[fuchsia::test]
283 async fn test_start_and_stop_component() {
284 let (proxy, mut stream) = create_proxy_and_stream::<RealmProxy_Marker>();
285 let realm =
286 TestRealm { realm_proxy: Some(proxy), lifecycle_senders: Mutex::new(Vec::new()) };
287
288 let task = fuchsia_async::Task::local(async move {
289 if let Some(Ok(RealmProxy_Request::ConnectToNamedProtocol {
290 protocol,
291 server_end,
292 responder,
293 })) = stream.next().await
294 {
295 assert_eq!(protocol, fsys2::LifecycleControllerMarker::PROTOCOL_NAME);
296 let _ = responder.send(Ok(()));
297 let mut lc_stream =
298 ServerEnd::<fsys2::LifecycleControllerMarker>::new(server_end).into_stream();
299 if let Some(Ok(fsys2::LifecycleControllerRequest::StartInstance {
300 moniker,
301 responder,
302 ..
303 })) = lc_stream.next().await
304 {
305 assert_eq!(moniker, "./test_start");
306 let _ = responder.send(Ok(()));
307 }
308 }
309 if let Some(Ok(RealmProxy_Request::ConnectToNamedProtocol {
310 protocol,
311 server_end,
312 responder,
313 })) = stream.next().await
314 {
315 assert_eq!(protocol, fsys2::LifecycleControllerMarker::PROTOCOL_NAME);
316 let _ = responder.send(Ok(()));
317 let mut lc_stream =
318 ServerEnd::<fsys2::LifecycleControllerMarker>::new(server_end).into_stream();
319 if let Some(Ok(fsys2::LifecycleControllerRequest::StopInstance {
320 moniker,
321 responder,
322 ..
323 })) = lc_stream.next().await
324 {
325 assert_eq!(moniker, "./test_stop");
326 let _ = responder.send(Ok(()));
327 }
328 }
329 });
330
331 realm.start_component("test_start").await.expect("start failed");
332 realm.stop_component("test_stop").await.expect("stop failed");
333 task.await;
334 }
335}