1pub use 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_fuchsia_io as fio;
17use fidl_next::{ClientEnd as NextClientEnd, ServerEnd as NextServerEnd};
18use fidl_next_fuchsia_component_runner::natural::ComponentNamespaceEntry;
19use fidl_next_fuchsia_driver_framework::DriverStartArgs;
20use fidl_next_fuchsia_driver_framework::natural::Offer as NextOffer;
21use fuchsia_async as fasync;
22use fuchsia_component::directory::open_directory_async;
23use fuchsia_component::server::{ServiceFs, ServiceObj};
24use futures::StreamExt;
25use std::marker::PhantomData;
26use std::sync::{Arc, Weak, mpsc};
27use zx::{HandleBased, Status};
28
29pub struct TestHarness<D> {
31 fdf_env_environment: Arc<Environment>,
32 node_manager: Arc<NodeManager>,
33 driver: Option<fdf_env::Driver<u32>>,
34 dispatcher: AutoReleaseDispatcher,
35 driver_incoming_dir: ClientEnd<fio::DirectoryMarker>,
36 config_vmo: Option<zx::Vmo>,
37 url: Option<String>,
38 offers: Option<Vec<NextOffer>>,
39 scope: fasync::Scope,
40 power_element_args: Option<fidl_fuchsia_driver_framework::PowerElementArgs>,
41 _d: PhantomData<D>,
42}
43
44impl<D: Driver> Default for TestHarness<D> {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50impl<D: Driver> TestHarness<D> {
51 pub fn new() -> Self {
53 let scope = fasync::Scope::new();
54 let mut driver_incoming = ServiceFs::new();
55 let env = Arc::new(Environment::start(0).unwrap());
56 let node_manager = NodeManager::new();
57 driver_incoming.dir("svc").add_service_connector(logsink_connector);
58
59 let (driver_incoming_dir_client, driver_incoming_dir_server) = zx::Channel::create();
60 driver_incoming.serve_connection(driver_incoming_dir_server.into()).unwrap();
61 let driver_incoming_dir = driver_incoming_dir_client.into();
62
63 scope.spawn(async move {
64 driver_incoming.collect::<()>().await;
65 });
66
67 let driver_value_ptr = Box::into_raw(Box::new(0x1234_u32));
69 let driver = env.new_driver(driver_value_ptr);
70 let env_clone = env.clone();
71 let dispatcher_builder =
72 DispatcherBuilder::new().name("test_harness").shutdown_observer(move |dispatcher| {
73 assert!(!env_clone.dispatcher_has_queued_tasks(dispatcher.as_dispatcher_ref()));
76 });
77 let dispatcher =
78 AutoReleaseDispatcher::from(driver.new_dispatcher(dispatcher_builder).unwrap());
79 let driver = Some(driver);
80
81 Self {
82 fdf_env_environment: env,
83 node_manager,
84 driver,
85 dispatcher,
86 driver_incoming_dir,
87 config_vmo: None,
88 url: None,
89 offers: None,
90 scope,
91 power_element_args: None,
92 _d: PhantomData,
93 }
94 }
95
96 pub fn set_driver_incoming(
98 mut self,
99 mut driver_incoming: ServiceFs<ServiceObj<'static, ()>>,
100 ) -> Self {
101 driver_incoming.dir("svc").add_service_connector(logsink_connector);
102
103 let (driver_incoming_dir_client, driver_incoming_dir_server) = zx::Channel::create();
104 driver_incoming.serve_connection(driver_incoming_dir_server.into()).unwrap();
105 let driver_incoming_dir = driver_incoming_dir_client.into();
106 self.scope.spawn(async move {
107 driver_incoming.collect::<()>().await;
108 });
109
110 self.driver_incoming_dir = driver_incoming_dir;
111 self
112 }
113
114 pub fn set_config(mut self, config: zx::Vmo) -> Self {
116 self.config_vmo = Some(config);
117 self
118 }
119
120 pub fn set_url(mut self, url: &str) -> Self {
122 self.url = Some(url.to_string());
123 self
124 }
125
126 pub fn add_offer(mut self, offer: Offer) -> Self {
128 self.offers.get_or_insert_default().push(convert::convert_df_offer(offer));
129 self
130 }
131
132 pub fn set_power_element_args(
134 mut self,
135 args: fidl_fuchsia_driver_framework::PowerElementArgs,
136 ) -> Self {
137 self.power_element_args = Some(args);
138 self
139 }
140
141 pub fn dispatcher(&self) -> WeakDispatcher {
143 WeakDispatcher::from(&self.dispatcher)
144 }
145
146 pub(crate) fn node_manager(&self) -> Weak<NodeManager> {
147 Arc::downgrade(&self.node_manager)
148 }
149
150 pub async fn start_driver(&mut self) -> Result<DriverUnderTest<'_, D>, Status> {
152 let (node_client, node_server) = zx::Channel::create();
153 let node_id = self.node_manager.create_root_node(node_server.into());
154
155 let (driver_outgoing_dir_client, driver_outgoing_dir_server) =
156 fidl::endpoints::create_endpoints();
157 let driver_outgoing = Incoming::from(driver_outgoing_dir_client);
158
159 let driver_incoming_svc =
160 open_directory_async(&self.driver_incoming_dir, "svc", fio::R_STAR_DIR).unwrap();
161
162 let start_args = DriverStartArgs {
163 node: Some(NextClientEnd::from_untyped(node_client)),
164 incoming: Some(vec![ComponentNamespaceEntry {
165 path: Some("/svc".to_string()),
166 directory: Some(NextClientEnd::from_untyped(
167 driver_incoming_svc.into_channel().unwrap().into(),
168 )),
169 }]),
170 outgoing_dir: Some(NextServerEnd::from_untyped(
171 driver_outgoing_dir_server.into_channel(),
172 )),
173 config: self
174 .config_vmo
175 .as_ref()
176 .and_then(|v| v.duplicate_handle(fidl::Rights::SAME_RIGHTS).ok()),
177 url: self.url.clone(),
178 node_offers: self.offers.clone(),
179 power_element_args: self.power_element_args.take().map(|args| {
180 fidl_next_fuchsia_driver_framework::natural::PowerElementArgs {
181 control_client: args
182 .control_client
183 .map(|c| NextClientEnd::from_untyped(c.into_channel())),
184 runner_server: args
185 .runner_server
186 .map(|s| NextServerEnd::from_untyped(s.into_channel())),
187 lessor_client: args
188 .lessor_client
189 .map(|c| NextClientEnd::from_untyped(c.into_channel())),
190 token: args.token,
191 }
192 }),
193 ..DriverStartArgs::default()
194 };
195
196 let mut driver =
197 DriverUnderTest::new(self, self.fdf_env_environment.clone(), driver_outgoing, node_id)
198 .await;
199 driver.start_driver(start_args).await?;
201 Ok(driver)
202 }
203}
204
205impl<D> Drop for TestHarness<D> {
206 fn drop(&mut self) {
207 let (shutdown_tx, shutdown_rx) = mpsc::channel();
208 self.driver.take().expect("driver").shutdown(move |driver_ref| {
209 let driver_value = unsafe { Box::from_raw(driver_ref.0 as *mut u32) };
211 assert_eq!(*driver_value, 0x1234);
212 shutdown_tx.send(()).unwrap();
213 });
214
215 shutdown_rx.recv().unwrap();
216
217 self.fdf_env_environment.destroy_all_dispatchers();
218 self.fdf_env_environment.reset();
219 }
220}
221
222mod convert {
223 use fidl_fuchsia_component_decl as decl;
224 use fidl_fuchsia_driver_framework as df;
225 use fidl_next_fuchsia_component_decl as decl_next;
226 use fidl_next_fuchsia_driver_framework as df_next;
227
228 pub fn convert_df_offer(offer: df::Offer) -> df_next::Offer {
229 match offer {
230 df::Offer::DictionaryOffer(o) => df_next::Offer::DictionaryOffer(convert_offer(o)),
231 df::Offer::ZirconTransport(o) => df_next::Offer::ZirconTransport(convert_offer(o)),
232 df::Offer::DriverTransport(o) => df_next::Offer::DriverTransport(convert_offer(o)),
233 df::Offer::__SourceBreaking { unknown_ordinal } => {
234 df_next::Offer::UnknownOrdinal_(unknown_ordinal)
235 }
236 }
237 }
238
239 fn convert_offer(offer: decl::Offer) -> decl_next::Offer {
240 match offer {
241 decl::Offer::Service(o) => decl_next::Offer::Service(decl_next::OfferService {
242 source: o.source.map(convert_ref),
243 source_name: o.source_name,
244 target: o.target.map(convert_ref),
245 target_name: o.target_name,
246 source_instance_filter: o.source_instance_filter,
247 renamed_instances: o
248 .renamed_instances
249 .map(|v| v.into_iter().map(convert_name_mapping).collect()),
250 availability: o.availability.map(convert_availability),
251 source_dictionary: o.source_dictionary,
252 dependency_type: o.dependency_type.map(convert_dependency_type),
253 }),
254 decl::Offer::Protocol(o) => decl_next::Offer::Protocol(decl_next::OfferProtocol {
255 source: o.source.map(convert_ref),
256 source_name: o.source_name,
257 target: o.target.map(convert_ref),
258 target_name: o.target_name,
259 dependency_type: o.dependency_type.map(convert_dependency_type),
260 availability: o.availability.map(convert_availability),
261 source_dictionary: o.source_dictionary,
262 }),
263 decl::Offer::Directory(o) => decl_next::Offer::Directory(decl_next::OfferDirectory {
264 source: o.source.map(convert_ref),
265 source_name: o.source_name,
266 target: o.target.map(convert_ref),
267 target_name: o.target_name,
268 availability: o.availability.map(convert_availability),
269 source_dictionary: o.source_dictionary,
270 dependency_type: o.dependency_type.map(convert_dependency_type),
271 rights: o.rights.map(convert_rights),
272 subdir: o.subdir,
273 }),
274 decl::Offer::Storage(o) => decl_next::Offer::Storage(decl_next::OfferStorage {
275 source_name: o.source_name,
276 source: o.source.map(convert_ref),
277 target: o.target.map(convert_ref),
278 target_name: o.target_name,
279 availability: o.availability.map(convert_availability),
280 }),
281 decl::Offer::Runner(o) => decl_next::Offer::Runner(decl_next::OfferRunner {
282 source: o.source.map(convert_ref),
283 source_name: o.source_name,
284 target: o.target.map(convert_ref),
285 target_name: o.target_name,
286 source_dictionary: o.source_dictionary,
287 }),
288 decl::Offer::Resolver(o) => decl_next::Offer::Resolver(decl_next::OfferResolver {
289 source: o.source.map(convert_ref),
290 source_name: o.source_name,
291 target: o.target.map(convert_ref),
292 target_name: o.target_name,
293 source_dictionary: o.source_dictionary,
294 }),
295 decl::Offer::EventStream(o) => {
296 decl_next::Offer::EventStream(decl_next::OfferEventStream {
297 source: o.source.map(convert_ref),
298 source_name: o.source_name,
299 scope: o.scope.map(|v| v.into_iter().map(convert_ref).collect()),
300 target: o.target.map(convert_ref),
301 target_name: o.target_name,
302 availability: o.availability.map(convert_availability),
303 })
304 }
305 decl::Offer::Dictionary(o) => {
306 decl_next::Offer::Dictionary(decl_next::OfferDictionary {
307 source: o.source.map(convert_ref),
308 source_name: o.source_name,
309 target: o.target.map(convert_ref),
310 target_name: o.target_name,
311 dependency_type: o.dependency_type.map(convert_dependency_type),
312 availability: o.availability.map(convert_availability),
313 source_dictionary: o.source_dictionary,
314 })
315 }
316 decl::Offer::Config(o) => decl_next::Offer::Config(decl_next::OfferConfiguration {
317 source: o.source.map(convert_ref),
318 source_name: o.source_name,
319 target: o.target.map(convert_ref),
320 target_name: o.target_name,
321 availability: o.availability.map(convert_availability),
322 source_dictionary: o.source_dictionary,
323 }),
324 decl::Offer::__SourceBreaking { unknown_ordinal } => {
325 decl_next::Offer::UnknownOrdinal_(unknown_ordinal)
326 }
327 }
328 }
329
330 fn convert_ref(ref_: decl::Ref) -> decl_next::Ref {
331 match ref_ {
332 decl::Ref::Parent(_) => decl_next::Ref::Parent(()),
333 decl::Ref::Self_(_) => decl_next::Ref::Self_(()),
334 decl::Ref::Child(child_ref) => decl_next::Ref::Child(decl_next::ChildRef {
335 name: child_ref.name,
336 collection: child_ref.collection,
337 }),
338 decl::Ref::Collection(collection_ref) => {
339 decl_next::Ref::Collection(decl_next::CollectionRef { name: collection_ref.name })
340 }
341 decl::Ref::Framework(_) => decl_next::Ref::Framework(()),
342 decl::Ref::Capability(capability_ref) => {
343 decl_next::Ref::Capability(decl_next::CapabilityRef { name: capability_ref.name })
344 }
345 decl::Ref::Debug(_) => decl_next::Ref::Debug(()),
346 decl::Ref::VoidType(_) => decl_next::Ref::VoidType(()),
347 decl::Ref::Environment(_) => decl_next::Ref::Environment(()),
348 decl::Ref::__SourceBreaking { unknown_ordinal } => {
349 decl_next::Ref::UnknownOrdinal_(unknown_ordinal)
350 }
351 }
352 }
353
354 fn convert_name_mapping(name_mapping: decl::NameMapping) -> decl_next::NameMapping {
355 fidl_next_fuchsia_component_decl::NameMapping {
356 source_name: name_mapping.source_name,
357 target_name: name_mapping.target_name,
358 }
359 }
360
361 fn convert_availability(availability: decl::Availability) -> decl_next::Availability {
362 match availability {
363 decl::Availability::Required => decl_next::Availability::Required,
364 decl::Availability::Optional => decl_next::Availability::Optional,
365 decl::Availability::SameAsTarget => decl_next::Availability::SameAsTarget,
366 decl::Availability::Transitional => decl_next::Availability::Transitional,
367 }
368 }
369
370 fn convert_dependency_type(dependency_type: decl::DependencyType) -> decl_next::DependencyType {
371 match dependency_type {
372 decl::DependencyType::Strong => decl_next::DependencyType::Strong,
373 decl::DependencyType::Weak => decl_next::DependencyType::Weak,
374 }
375 }
376
377 fn convert_rights(rights: fidl_fuchsia_io::Operations) -> fidl_next_fuchsia_io::Operations {
378 fidl_next_fuchsia_io::Operations::from_bits_retain(rights.bits())
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385 use crate::{Node, NodeBuilder, ServiceInstance, ServiceOffer};
386 use fidl_next::{Request, Responder};
387 use fidl_next_fuchsia_examples as fexample;
388 use fidl_next_fuchsia_examples::echo::{EchoString, SendString};
389 use fuchsia_async as fasync;
390 use futures::StreamExt;
391 use futures::lock::Mutex;
392 use log::info;
393
394 struct EchoServer;
395
396 impl fexample::EchoServerHandler<zx::Channel> for EchoServer {
397 async fn echo_string(
398 &mut self,
399 request: Request<EchoString, zx::Channel>,
400 responder: Responder<EchoString, zx::Channel>,
401 ) {
402 info!("ECHO: {}", request.payload().value);
403 responder.respond("resp").await.unwrap();
404 }
405
406 async fn send_string(&mut self, _request: Request<SendString, zx::Channel>) {}
407 }
408
409 struct Service {
410 scope: fasync::ScopeHandle,
411 }
412
413 impl fexample::EchoServiceHandler for Service {
414 fn regular_echo(&self, server_end: NextServerEnd<fexample::Echo>) {
415 server_end.spawn_on(EchoServer, &self.scope);
416 }
417
418 fn reversed_echo(&self, _server_end: NextServerEnd<fexample::Echo>) {}
419 }
420
421 #[allow(dead_code)]
422 struct TestDriver {
423 node: Node,
424 scope: fasync::Scope,
425 tmp: Mutex<String>,
426 }
427
428 impl TestDriver {
429 async fn set_tmp(&self, resp: &str) {
430 let mut tmp = self.tmp.lock().await;
431 *tmp = resp.to_string();
432 }
433
434 async fn get_tmp(&self) -> String {
435 let tmp = self.tmp.lock().await;
436 tmp.to_string()
437 }
438 }
439
440 impl Driver for TestDriver {
441 const NAME: &'static str = "test-driver";
442
443 async fn start(mut context: crate::DriverContext) -> Result<Self, Status> {
444 let service_proxy: ServiceInstance<fexample::EchoService> =
445 context.incoming.service().connect_next()?;
446 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
447 service_proxy.regular_echo(server_end).unwrap();
448 let client = client_end.spawn();
449 let resp =
450 client.echo_string("echo from driver").await.map_err(|_| Status::IO_REFUSED)?;
451 assert_eq!("resp", resp.response.as_str());
452
453 let scope = fasync::Scope::new_with_name("test driver scope");
454 let mut outgoing = ServiceFs::new();
455 let offer = ServiceOffer::<fexample::EchoService>::new_next()
456 .add_named_next(&mut outgoing, "default", Service { scope: scope.to_handle() })
457 .build_zircon_offer_next();
458 context.serve_outgoing(&mut outgoing)?;
459 scope.spawn(outgoing.collect());
460
461 let node = context.take_node()?;
462 let child_node = NodeBuilder::new("transport-child")
463 .add_property("prop", "val")
464 .add_offer(offer)
465 .build();
466 node.add_child(child_node).await?;
467
468 info!("TestDriver started");
469 Ok(Self { node, scope, tmp: Mutex::new("NA".to_string()) })
470 }
471
472 async fn stop(&self) {
473 info!("TestDriver stopped. Tmp: '{}'", *self.tmp.lock().await);
474 }
475 }
476
477 #[fuchsia::test]
478 async fn test_basic() {
479 let scope = fasync::Scope::new_with_name("test scope");
480 let mut service_fs = ServiceFs::new();
481 let offer = ServiceOffer::<fexample::EchoService>::new_next()
482 .add_named_next(&mut service_fs, "default", Service { scope: scope.to_handle() })
483 .build_zircon_offer_next();
484 let mut harness = TestHarness::<TestDriver>::new()
485 .set_driver_incoming(service_fs)
486 .set_url("test_url")
487 .add_offer(offer);
488
489 let start_result = harness.start_driver().await;
490 let started_driver = start_result.expect("success");
491 let driver = started_driver.get_driver().expect("failed to get driver");
492 driver.set_tmp("my_temp_var").await;
493 assert_eq!("my_temp_var", driver.get_tmp().await);
494
495 let service_proxy: ServiceInstance<fexample::EchoService> =
496 started_driver.driver_outgoing().service().connect_next().unwrap();
497 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
498 service_proxy.regular_echo(server_end).unwrap();
499 let client = client_end.spawn();
500 let resp = client.echo_string("echo to driver").await.unwrap();
501 assert_eq!("resp", resp.response.as_str());
502 started_driver.stop_driver().await;
503 }
504
505 #[fuchsia::test]
506 async fn test_multiple_start_stop() {
507 let scope = fasync::Scope::new_with_name("test scope");
508 let mut service_fs = ServiceFs::new();
509 let offer = ServiceOffer::<fexample::EchoService>::new_next()
510 .add_named_next(&mut service_fs, "default", Service { scope: scope.to_handle() })
511 .build_zircon_offer_next();
512 let mut harness = TestHarness::<TestDriver>::new()
513 .set_driver_incoming(service_fs)
514 .set_url("test_url")
515 .add_offer(offer);
516
517 for i in 1..=3 {
518 let start_result = harness.start_driver().await;
519 let started_driver = start_result.expect("success");
520 let driver = started_driver.get_driver().expect("failed to get driver");
521 driver.set_tmp(format!("my_temp_var_{}", i).as_str()).await;
522 assert_eq!(format!("my_temp_var_{}", i), driver.get_tmp().await);
523
524 let service_proxy: ServiceInstance<fexample::EchoService> =
525 started_driver.driver_outgoing().service().connect_next().unwrap();
526 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
527 service_proxy.regular_echo(server_end).unwrap();
528 let client = client_end.spawn();
529 let resp = client.echo_string("echo to driver").await.unwrap();
530 assert_eq!("resp", resp.response.as_str());
531 started_driver.stop_driver().await;
532 }
533 }
534
535 #[fuchsia::test]
536 async fn test_no_start() {
537 let _harness = TestHarness::<TestDriver>::default();
538 }
539
540 #[fuchsia::test]
541 async fn test_start_fail() {
542 let mut harness = TestHarness::<TestDriver>::new();
543 let start_result = harness.start_driver().await;
544 assert_eq!(start_result.err(), Some(Status::IO_REFUSED));
545 }
546}