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