1use crate::testing::dut::DriverUnderTest;
8use crate::testing::logsink_connector;
9use crate::testing::node::NodeManager;
10use crate::{Driver, Incoming};
11use anyhow::Result;
12use fdf::{AutoReleaseDispatcher, DispatcherBuilder, WeakDispatcher};
13use fdf_env::Environment;
14use fidl::endpoints::{ClientEnd, Proxy};
15use fidl_fuchsia_driver_framework::Offer;
16use fidl_next::{ClientEnd as NextClientEnd, CompatFrom, ServerEnd as NextServerEnd};
17use fidl_next_fuchsia_component_runner::natural::ComponentNamespaceEntry;
18use fidl_next_fuchsia_driver_framework::DriverStartArgs;
19use fidl_next_fuchsia_driver_framework::natural::Offer as NextOffer;
20use fuchsia_component::directory::open_directory_async;
21use fuchsia_component::server::{ServiceFs, ServiceObj};
22use futures::StreamExt;
23use std::marker::PhantomData;
24use std::sync::{Arc, Weak, mpsc};
25use zx::{HandleBased, Status};
26use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
27
28pub struct TestHarness<D> {
30 fdf_env_environment: Arc<Environment>,
31 node_manager: Arc<NodeManager>,
32 driver: Option<fdf_env::Driver<u32>>,
33 dispatcher: AutoReleaseDispatcher,
34 driver_incoming_dir: ClientEnd<fio::DirectoryMarker>,
35 config_vmo: Option<zx::Vmo>,
36 url: Option<String>,
37 offers: Option<Vec<Offer>>,
38 scope: fasync::Scope,
39 _d: PhantomData<D>,
40}
41
42impl<D: Driver> Default for TestHarness<D> {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl<D: Driver> TestHarness<D> {
49 pub fn new() -> Self {
51 let scope = fasync::Scope::new();
52 let mut driver_incoming = ServiceFs::new();
53 let env = Arc::new(Environment::start(0).unwrap());
54 let node_manager = NodeManager::new();
55 driver_incoming.dir("svc").add_service_connector(logsink_connector);
56
57 let (driver_incoming_dir_client, driver_incoming_dir_server) = zx::Channel::create();
58 driver_incoming.serve_connection(driver_incoming_dir_server.into()).unwrap();
59 let driver_incoming_dir = driver_incoming_dir_client.into();
60
61 scope.spawn(async move {
62 driver_incoming.collect::<()>().await;
63 });
64
65 let driver_value_ptr = Box::into_raw(Box::new(0x1234_u32));
67 let driver = env.new_driver(driver_value_ptr);
68 let env_clone = env.clone();
69 let dispatcher_builder =
70 DispatcherBuilder::new().name("test_harness").shutdown_observer(move |dispatcher| {
71 assert!(!env_clone.dispatcher_has_queued_tasks(dispatcher.as_dispatcher_ref()));
74 });
75 let dispatcher =
76 AutoReleaseDispatcher::from(driver.new_dispatcher(dispatcher_builder).unwrap());
77 let driver = Some(driver);
78
79 Self {
80 fdf_env_environment: env,
81 node_manager,
82 driver,
83 dispatcher,
84 driver_incoming_dir,
85 config_vmo: None,
86 url: None,
87 offers: None,
88 scope,
89 _d: PhantomData,
90 }
91 }
92
93 pub fn set_driver_incoming(
95 mut self,
96 mut driver_incoming: ServiceFs<ServiceObj<'static, ()>>,
97 ) -> Self {
98 driver_incoming.dir("svc").add_service_connector(logsink_connector);
99
100 let (driver_incoming_dir_client, driver_incoming_dir_server) = zx::Channel::create();
101 driver_incoming.serve_connection(driver_incoming_dir_server.into()).unwrap();
102 let driver_incoming_dir = driver_incoming_dir_client.into();
103 self.scope.spawn(async move {
104 driver_incoming.collect::<()>().await;
105 });
106
107 self.driver_incoming_dir = driver_incoming_dir;
108 self
109 }
110
111 pub fn set_config(mut self, config: zx::Vmo) -> Self {
113 self.config_vmo = Some(config);
114 self
115 }
116
117 pub fn set_url(mut self, url: &str) -> Self {
119 self.url = Some(url.to_string());
120 self
121 }
122
123 pub fn add_offer(mut self, offer: Offer) -> Self {
125 self.offers.get_or_insert_default().push(offer);
126 self
127 }
128
129 pub fn dispatcher(&self) -> WeakDispatcher {
131 WeakDispatcher::from(&self.dispatcher)
132 }
133
134 pub(crate) fn node_manager(&self) -> Weak<NodeManager> {
135 Arc::downgrade(&self.node_manager)
136 }
137
138 pub async fn start_driver(&mut self) -> Result<DriverUnderTest<'_, D>, Status> {
140 let (node_client, node_server) = zx::Channel::create();
141 let node_id = self.node_manager.create_root_node(node_server.into());
142
143 let (driver_outgoing_dir_client, driver_outgoing_dir_server) =
144 fidl::endpoints::create_endpoints();
145 let driver_outgoing = Incoming::from(driver_outgoing_dir_client);
146
147 let driver_incoming_svc =
148 open_directory_async(&self.driver_incoming_dir, "svc", fio::R_STAR_DIR).unwrap();
149
150 let start_args = DriverStartArgs {
151 node: Some(NextClientEnd::from_untyped(node_client)),
152 incoming: Some(vec![ComponentNamespaceEntry {
153 path: Some("/svc".to_string()),
154 directory: Some(NextClientEnd::from_untyped(
155 driver_incoming_svc.into_channel().unwrap().into(),
156 )),
157 }]),
158 outgoing_dir: Some(NextServerEnd::compat_from(driver_outgoing_dir_server)),
159 config: self
160 .config_vmo
161 .as_ref()
162 .and_then(|v| v.duplicate_handle(fidl::Rights::SAME_RIGHTS).ok()),
163 url: self.url.clone(),
164 node_offers: self
165 .offers
166 .as_ref()
167 .map(|o| o.clone().into_iter().map(NextOffer::compat_from).collect()),
168 ..DriverStartArgs::default()
169 };
170
171 let mut driver =
172 DriverUnderTest::new(self, self.fdf_env_environment.clone(), driver_outgoing, node_id)
173 .await;
174 driver.start_driver(start_args).await?;
176 Ok(driver)
177 }
178}
179
180impl<D> Drop for TestHarness<D> {
181 fn drop(&mut self) {
182 let (shutdown_tx, shutdown_rx) = mpsc::channel();
183 self.driver.take().expect("driver").shutdown(move |driver_ref| {
184 let driver_value = unsafe { Box::from_raw(driver_ref.0 as *mut u32) };
186 assert_eq!(*driver_value, 0x1234);
187 shutdown_tx.send(()).unwrap();
188 });
189
190 shutdown_rx.recv().unwrap();
191
192 self.fdf_env_environment.destroy_all_dispatchers();
193 self.fdf_env_environment.reset();
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::{Node, NodeBuilder, ServiceInstance, ServiceOffer};
201 use fidl_next::{Request, Responder};
202 use fidl_next_fuchsia_examples::echo::{EchoString, SendString};
203 use futures::StreamExt;
204 use futures::lock::Mutex;
205 use log::info;
206 use {fidl_next_fuchsia_examples as fexample, fuchsia_async as fasync};
207
208 struct EchoServer;
209
210 impl fexample::EchoServerHandler<zx::Channel> for EchoServer {
211 async fn echo_string(
212 &mut self,
213 request: Request<EchoString, zx::Channel>,
214 responder: Responder<EchoString, zx::Channel>,
215 ) {
216 info!("ECHO: {}", request.payload().value);
217 responder.respond("resp").await.unwrap();
218 }
219
220 async fn send_string(&mut self, _request: Request<SendString, zx::Channel>) {}
221 }
222
223 struct Service {
224 scope: fasync::ScopeHandle,
225 }
226
227 impl fexample::EchoServiceHandler for Service {
228 fn regular_echo(&self, server_end: NextServerEnd<fexample::Echo>) {
229 server_end.spawn_on(EchoServer, &self.scope).detach_on_drop();
230 }
231
232 fn reversed_echo(&self, _server_end: NextServerEnd<fexample::Echo>) {}
233 }
234
235 #[allow(dead_code)]
236 struct TestDriver {
237 node: Node,
238 scope: fasync::Scope,
239 tmp: Mutex<String>,
240 }
241
242 impl TestDriver {
243 async fn set_tmp(&self, resp: &str) {
244 let mut tmp = self.tmp.lock().await;
245 *tmp = resp.to_string();
246 }
247
248 async fn get_tmp(&self) -> String {
249 let tmp = self.tmp.lock().await;
250 tmp.to_string()
251 }
252 }
253
254 impl Driver for TestDriver {
255 const NAME: &'static str = "test-driver";
256
257 async fn start(mut context: crate::DriverContext) -> Result<Self, Status> {
258 let service_proxy: ServiceInstance<fexample::EchoService> =
259 context.incoming.service().connect_next()?;
260 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
261 service_proxy.regular_echo(server_end).unwrap();
262 let client = client_end.spawn();
263 let resp =
264 client.echo_string("echo from driver").await.map_err(|_| Status::IO_REFUSED)?;
265 assert_eq!("resp", resp.response.as_str());
266
267 let scope = fasync::Scope::new_with_name("test driver scope");
268 let mut outgoing = ServiceFs::new();
269 let offer = ServiceOffer::<fexample::EchoService>::new_next()
270 .add_named_next(&mut outgoing, "default", Service { scope: scope.to_handle() })
271 .build_zircon_offer_next();
272 context.serve_outgoing(&mut outgoing)?;
273 scope.spawn(outgoing.collect());
274
275 let node = context.take_node()?;
276 let child_node = NodeBuilder::new("transport-child")
277 .add_property("prop", "val")
278 .add_offer(offer)
279 .build();
280 node.add_child(child_node).await?;
281
282 info!("TestDriver started");
283 Ok(Self { node, scope, tmp: Mutex::new("NA".to_string()) })
284 }
285
286 async fn stop(&self) {
287 info!("TestDriver stopped. Tmp: '{}'", *self.tmp.lock().await);
288 }
289 }
290
291 #[fuchsia::test]
292 async fn test_basic() {
293 let scope = fasync::Scope::new_with_name("test scope");
294 let mut service_fs = ServiceFs::new();
295 let offer = ServiceOffer::<fexample::EchoService>::new_next()
296 .add_named_next(&mut service_fs, "default", Service { scope: scope.to_handle() })
297 .build_zircon_offer_next();
298 let mut harness = TestHarness::<TestDriver>::new()
299 .set_driver_incoming(service_fs)
300 .set_url("test_url")
301 .add_offer(offer);
302
303 let start_result = harness.start_driver().await;
304 let started_driver = start_result.expect("success");
305 let driver = started_driver.get_driver().expect("failed to get driver");
306 driver.set_tmp("my_temp_var").await;
307 assert_eq!("my_temp_var", driver.get_tmp().await);
308
309 let service_proxy: ServiceInstance<fexample::EchoService> =
310 started_driver.driver_outgoing().service().connect_next().unwrap();
311 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
312 service_proxy.regular_echo(server_end).unwrap();
313 let client = client_end.spawn();
314 let resp = client.echo_string("echo to driver").await.unwrap();
315 assert_eq!("resp", resp.response.as_str());
316 started_driver.stop_driver().await;
317 }
318
319 #[fuchsia::test]
320 async fn test_multiple_start_stop() {
321 let scope = fasync::Scope::new_with_name("test scope");
322 let mut service_fs = ServiceFs::new();
323 let offer = ServiceOffer::<fexample::EchoService>::new_next()
324 .add_named_next(&mut service_fs, "default", Service { scope: scope.to_handle() })
325 .build_zircon_offer_next();
326 let mut harness = TestHarness::<TestDriver>::new()
327 .set_driver_incoming(service_fs)
328 .set_url("test_url")
329 .add_offer(offer);
330
331 for i in 1..=3 {
332 let start_result = harness.start_driver().await;
333 let started_driver = start_result.expect("success");
334 let driver = started_driver.get_driver().expect("failed to get driver");
335 driver.set_tmp(format!("my_temp_var_{}", i).as_str()).await;
336 assert_eq!(format!("my_temp_var_{}", i), driver.get_tmp().await);
337
338 let service_proxy: ServiceInstance<fexample::EchoService> =
339 started_driver.driver_outgoing().service().connect_next().unwrap();
340 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
341 service_proxy.regular_echo(server_end).unwrap();
342 let client = client_end.spawn();
343 let resp = client.echo_string("echo to driver").await.unwrap();
344 assert_eq!("resp", resp.response.as_str());
345 started_driver.stop_driver().await;
346 }
347 }
348
349 #[fuchsia::test]
350 async fn test_no_start() {
351 let _harness = TestHarness::<TestDriver>::default();
352 }
353
354 #[fuchsia::test]
355 async fn test_start_fail() {
356 let mut harness = TestHarness::<TestDriver>::new();
357 let start_result = harness.start_driver().await;
358 assert_eq!(start_result.err(), Some(Status::IO_REFUSED));
359 }
360}