1pub use crate::testing::dut::DriverUnderTest;
8use crate::testing::logsink_connector;
9use crate::testing::node::NodeManager;
10use crate::{Driver, Incoming, PropertyValue};
11use anyhow::Result;
12use fdf::{AsyncDispatcher, DispatcherBuilder};
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 as df_next;
20use fidl_next_fuchsia_driver_framework::DriverStartArgs;
21use fidl_next_fuchsia_driver_framework::natural::Offer as NextOffer;
22use fuchsia_async as fasync;
23use fuchsia_component::directory::open_directory_async;
24use fuchsia_component::server::{ServiceFs, ServiceObj};
25use futures::StreamExt;
26use std::marker::PhantomData;
27use std::sync::{Arc, Weak, mpsc};
28use zx::Status;
29
30pub struct TestHarness<D> {
32 fdf_env_environment: Arc<Environment>,
33 node_manager: Arc<NodeManager>,
34 driver: Option<fdf_env::Driver<u32>>,
35 dispatcher: AsyncDispatcher,
36 driver_incoming_dir: ClientEnd<fio::DirectoryMarker>,
37 config_vmo: Option<zx::Vmo>,
38 url: Option<String>,
39 node_name: Option<String>,
40 offers: Option<Vec<NextOffer>>,
41 scope: fasync::Scope,
42 power_element_args: Option<df_next::PowerElementArgs>,
43 node_token: Option<zx::Event>,
44 node_properties: Vec<df_next::NodePropertyEntry2>,
45 _d: PhantomData<D>,
46}
47
48impl<D: Driver> Default for TestHarness<D> {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl<D: Driver> TestHarness<D> {
55 pub fn new() -> Self {
57 let scope = fasync::Scope::new();
58 let mut driver_incoming = ServiceFs::new();
59 let env = Arc::new(Environment::start(0).unwrap());
60 let node_manager = NodeManager::new();
61 driver_incoming.dir("svc").add_service_connector(logsink_connector);
62
63 let (driver_incoming_dir_client, driver_incoming_dir_server) = zx::Channel::create();
64 driver_incoming.serve_connection(driver_incoming_dir_server.into()).unwrap();
65 let driver_incoming_dir = driver_incoming_dir_client.into();
66
67 scope.spawn(async move {
68 driver_incoming.collect::<()>().await;
69 });
70
71 let driver_value_ptr = Box::into_raw(Box::new(0x1234_u32));
73 let driver = env.new_driver(driver_value_ptr);
74 let env_clone = env.clone();
75 let dispatcher_builder =
76 DispatcherBuilder::new().name("test_harness").shutdown_observer(move |dispatcher| {
77 assert!(!env_clone.dispatcher_has_queued_tasks(dispatcher.as_dispatcher_ref()));
80 });
81 let dispatcher =
82 AsyncDispatcher::new(&driver.new_dispatcher(dispatcher_builder).unwrap().release());
83 let driver = Some(driver);
84
85 Self {
86 fdf_env_environment: env,
87 node_manager,
88 driver,
89 dispatcher,
90 driver_incoming_dir,
91 config_vmo: None,
92 url: None,
93 node_name: None,
94 offers: None,
95 scope,
96 power_element_args: None,
97 node_token: None,
98 node_properties: Vec::new(),
99 _d: PhantomData,
100 }
101 }
102
103 pub fn set_driver_incoming(
105 mut self,
106 mut driver_incoming: ServiceFs<ServiceObj<'static, ()>>,
107 ) -> Self {
108 driver_incoming.dir("svc").add_service_connector(logsink_connector);
109
110 let (driver_incoming_dir_client, driver_incoming_dir_server) = zx::Channel::create();
111 driver_incoming.serve_connection(driver_incoming_dir_server.into()).unwrap();
112 let driver_incoming_dir = driver_incoming_dir_client.into();
113 self.scope.spawn(async move {
114 driver_incoming.collect::<()>().await;
115 });
116
117 self.driver_incoming_dir = driver_incoming_dir;
118 self
119 }
120
121 pub fn set_config(mut self, config: zx::Vmo) -> Self {
123 self.config_vmo = Some(config);
124 self
125 }
126
127 pub fn set_url(mut self, url: &str) -> Self {
129 self.url = Some(url.to_string());
130 self
131 }
132
133 pub fn set_node_name(mut self, name: impl Into<String>) -> Self {
135 self.node_name = Some(name.into());
136 self
137 }
138
139 pub fn add_offer(mut self, offer: Offer) -> Self {
141 self.offers.get_or_insert_default().push(convert::convert_df_offer(offer));
142 self
143 }
144
145 pub fn set_power_element_args(mut self, args: impl IntoPowerElementArgs) -> Self {
150 self.power_element_args = Some(args.into_power_element_args());
151 self
152 }
153
154 pub fn set_node_token(mut self, token: zx::Event) -> Self {
156 self.node_token = Some(token);
157 self
158 }
159
160 pub fn set_power_element_token(mut self, token: zx::Event) -> Self {
165 self.power_element_args.get_or_insert_default().token = Some(token);
166 self
167 }
168
169 pub fn add_node_property(
173 mut self,
174 parent: &str,
175 key: impl Into<String>,
176 value: impl Into<PropertyValue>,
177 ) -> Self {
178 let property = df_next::NodeProperty2 {
179 key: key.into(),
180 value: convert::convert_property_value(value.into()),
181 };
182 match self.node_properties.iter_mut().find(|entry| entry.name == parent) {
183 Some(entry) => entry.properties.push(property),
184 None => self.node_properties.push(df_next::NodePropertyEntry2 {
185 name: parent.to_string(),
186 properties: vec![property],
187 }),
188 }
189 self
190 }
191
192 pub fn dispatcher(&self) -> AsyncDispatcher {
194 self.dispatcher.clone()
195 }
196
197 pub(crate) fn node_manager(&self) -> Weak<NodeManager> {
198 Arc::downgrade(&self.node_manager)
199 }
200
201 pub async fn start_driver(&mut self) -> Result<DriverUnderTest<'_, D>, Status> {
203 let (node_client, node_server) = zx::Channel::create();
204 let node_id = self.node_manager.create_root_node(node_server.into());
205
206 let (driver_outgoing_dir_client, driver_outgoing_dir_server) =
207 fidl::endpoints::create_endpoints();
208 let driver_outgoing = Incoming::from(driver_outgoing_dir_client);
209
210 let driver_incoming_svc =
211 open_directory_async(&self.driver_incoming_dir, ".", fio::R_STAR_DIR).unwrap();
212
213 let start_args = DriverStartArgs {
214 node: Some(NextClientEnd::from_untyped(node_client)),
215 incoming: Some(vec![ComponentNamespaceEntry {
216 path: Some("/".to_string()),
217 directory: Some(NextClientEnd::from_untyped(
218 driver_incoming_svc.into_channel().unwrap().into(),
219 )),
220 }]),
221 outgoing_dir: Some(NextServerEnd::from_untyped(
222 driver_outgoing_dir_server.into_channel(),
223 )),
224 config: self
225 .config_vmo
226 .as_ref()
227 .and_then(|v| v.duplicate_handle(fidl::Rights::SAME_RIGHTS).ok()),
228 url: self.url.clone(),
229 node_name: self.node_name.clone(),
230 node_offers: self.offers.clone(),
231 power_element_args: self.power_element_args.take(),
232 node_token: self
233 .node_token
234 .as_ref()
235 .and_then(|t| t.duplicate_handle(fidl::Rights::SAME_RIGHTS).ok()),
236 node_properties_2: (!self.node_properties.is_empty())
237 .then(|| self.node_properties.clone()),
238 ..DriverStartArgs::default()
239 };
240
241 let mut driver =
242 DriverUnderTest::new(self, self.fdf_env_environment.clone(), driver_outgoing, node_id)
243 .await;
244 driver.start_driver(start_args).await?;
246 Ok(driver)
247 }
248}
249
250impl<D> Drop for TestHarness<D> {
251 fn drop(&mut self) {
252 let (shutdown_tx, shutdown_rx) = mpsc::channel();
253 self.driver.take().expect("driver").shutdown(move |driver_ref| {
254 let driver_value = unsafe { Box::from_raw(driver_ref.0 as *mut u32) };
256 assert_eq!(*driver_value, 0x1234);
257 shutdown_tx.send(()).unwrap();
258 });
259
260 shutdown_rx.recv().unwrap();
261
262 self.fdf_env_environment.destroy_all_dispatchers();
263 self.fdf_env_environment.reset();
264 }
265}
266
267pub trait IntoPowerElementArgs {
273 fn into_power_element_args(self) -> df_next::PowerElementArgs;
275}
276
277impl IntoPowerElementArgs for df_next::PowerElementArgs {
278 fn into_power_element_args(self) -> df_next::PowerElementArgs {
279 self
280 }
281}
282
283impl IntoPowerElementArgs for fidl_fuchsia_driver_framework::PowerElementArgs {
284 fn into_power_element_args(self) -> df_next::PowerElementArgs {
285 df_next::PowerElementArgs {
286 control_client: self
287 .control_client
288 .map(|c| NextClientEnd::from_untyped(c.into_channel())),
289 runner_server: self
290 .runner_server
291 .map(|s| NextServerEnd::from_untyped(s.into_channel())),
292 lessor_client: self
293 .lessor_client
294 .map(|c| NextClientEnd::from_untyped(c.into_channel())),
295 token: self.token,
296 }
297 }
298}
299
300mod convert {
301 use fidl_fuchsia_component_decl as decl;
302 use fidl_fuchsia_driver_framework as df;
303 use fidl_next_fuchsia_component_decl as decl_next;
304 use fidl_next_fuchsia_driver_framework as df_next;
305
306 pub fn convert_property_value(value: crate::PropertyValue) -> df_next::NodePropertyValue {
307 match value.0 {
308 df::NodePropertyValue::IntValue(v) => df_next::NodePropertyValue::IntValue(v),
309 df::NodePropertyValue::StringValue(v) => df_next::NodePropertyValue::StringValue(v),
310 df::NodePropertyValue::BoolValue(v) => df_next::NodePropertyValue::BoolValue(v),
311 df::NodePropertyValue::EnumValue(v) => df_next::NodePropertyValue::EnumValue(v),
312 df::NodePropertyValue::__SourceBreaking { unknown_ordinal } => {
313 df_next::NodePropertyValue::UnknownOrdinal_(unknown_ordinal)
314 }
315 }
316 }
317
318 pub fn convert_df_offer(offer: df::Offer) -> df_next::Offer {
319 match offer {
320 df::Offer::DictionaryOffer(o) => df_next::Offer::DictionaryOffer(convert_offer(o)),
321 df::Offer::ZirconTransport(o) => df_next::Offer::ZirconTransport(convert_offer(o)),
322 df::Offer::DriverTransport(o) => df_next::Offer::DriverTransport(convert_offer(o)),
323 df::Offer::__SourceBreaking { unknown_ordinal } => {
324 df_next::Offer::UnknownOrdinal_(unknown_ordinal)
325 }
326 }
327 }
328
329 fn convert_offer(offer: decl::Offer) -> decl_next::Offer {
330 match offer {
331 decl::Offer::Service(o) => decl_next::Offer::Service(decl_next::OfferService {
332 source: o.source.map(convert_ref),
333 source_name: o.source_name,
334 target: o.target.map(convert_ref),
335 target_name: o.target_name,
336 source_instance_filter: o.source_instance_filter,
337 renamed_instances: o
338 .renamed_instances
339 .map(|v| v.into_iter().map(convert_name_mapping).collect()),
340 availability: o.availability.map(convert_availability),
341 source_dictionary: o.source_dictionary,
342 dependency_type: o.dependency_type.map(convert_dependency_type),
343 }),
344 decl::Offer::Protocol(o) => decl_next::Offer::Protocol(decl_next::OfferProtocol {
345 source: o.source.map(convert_ref),
346 source_name: o.source_name,
347 target: o.target.map(convert_ref),
348 target_name: o.target_name,
349 dependency_type: o.dependency_type.map(convert_dependency_type),
350 availability: o.availability.map(convert_availability),
351 source_dictionary: o.source_dictionary,
352 }),
353 decl::Offer::Directory(o) => decl_next::Offer::Directory(decl_next::OfferDirectory {
354 source: o.source.map(convert_ref),
355 source_name: o.source_name,
356 target: o.target.map(convert_ref),
357 target_name: o.target_name,
358 availability: o.availability.map(convert_availability),
359 source_dictionary: o.source_dictionary,
360 dependency_type: o.dependency_type.map(convert_dependency_type),
361 rights: o.rights.map(convert_rights),
362 subdir: o.subdir,
363 }),
364 decl::Offer::Storage(o) => decl_next::Offer::Storage(decl_next::OfferStorage {
365 source_name: o.source_name,
366 source: o.source.map(convert_ref),
367 target: o.target.map(convert_ref),
368 target_name: o.target_name,
369 availability: o.availability.map(convert_availability),
370 }),
371 decl::Offer::Runner(o) => decl_next::Offer::Runner(decl_next::OfferRunner {
372 source: o.source.map(convert_ref),
373 source_name: o.source_name,
374 target: o.target.map(convert_ref),
375 target_name: o.target_name,
376 source_dictionary: o.source_dictionary,
377 }),
378 decl::Offer::Resolver(o) => decl_next::Offer::Resolver(decl_next::OfferResolver {
379 source: o.source.map(convert_ref),
380 source_name: o.source_name,
381 target: o.target.map(convert_ref),
382 target_name: o.target_name,
383 source_dictionary: o.source_dictionary,
384 }),
385 decl::Offer::EventStream(o) => {
386 decl_next::Offer::EventStream(decl_next::OfferEventStream {
387 source: o.source.map(convert_ref),
388 source_name: o.source_name,
389 scope: o.scope.map(|v| v.into_iter().map(convert_ref).collect()),
390 target: o.target.map(convert_ref),
391 target_name: o.target_name,
392 availability: o.availability.map(convert_availability),
393 })
394 }
395 decl::Offer::Dictionary(o) => {
396 decl_next::Offer::Dictionary(decl_next::OfferDictionary {
397 source: o.source.map(convert_ref),
398 source_name: o.source_name,
399 target: o.target.map(convert_ref),
400 target_name: o.target_name,
401 dependency_type: o.dependency_type.map(convert_dependency_type),
402 availability: o.availability.map(convert_availability),
403 source_dictionary: o.source_dictionary,
404 })
405 }
406 decl::Offer::Config(o) => decl_next::Offer::Config(decl_next::OfferConfiguration {
407 source: o.source.map(convert_ref),
408 source_name: o.source_name,
409 target: o.target.map(convert_ref),
410 target_name: o.target_name,
411 availability: o.availability.map(convert_availability),
412 source_dictionary: o.source_dictionary,
413 }),
414 decl::Offer::__SourceBreaking { unknown_ordinal } => {
415 decl_next::Offer::UnknownOrdinal_(unknown_ordinal)
416 }
417 }
418 }
419
420 fn convert_ref(ref_: decl::Ref) -> decl_next::Ref {
421 match ref_ {
422 decl::Ref::Parent(_) => decl_next::Ref::Parent(()),
423 decl::Ref::Self_(_) => decl_next::Ref::Self_(()),
424 decl::Ref::Child(child_ref) => decl_next::Ref::Child(decl_next::ChildRef {
425 name: child_ref.name,
426 collection: child_ref.collection,
427 }),
428 decl::Ref::Collection(collection_ref) => {
429 decl_next::Ref::Collection(decl_next::CollectionRef { name: collection_ref.name })
430 }
431 decl::Ref::Framework(_) => decl_next::Ref::Framework(()),
432 decl::Ref::Capability(capability_ref) => {
433 decl_next::Ref::Capability(decl_next::CapabilityRef { name: capability_ref.name })
434 }
435 decl::Ref::Debug(_) => decl_next::Ref::Debug(()),
436 decl::Ref::VoidType(_) => decl_next::Ref::VoidType(()),
437 decl::Ref::Environment(_) => decl_next::Ref::Environment(()),
438 decl::Ref::__SourceBreaking { unknown_ordinal } => {
439 decl_next::Ref::UnknownOrdinal_(unknown_ordinal)
440 }
441 }
442 }
443
444 fn convert_name_mapping(name_mapping: decl::NameMapping) -> decl_next::NameMapping {
445 fidl_next_fuchsia_component_decl::NameMapping {
446 source_name: name_mapping.source_name,
447 target_name: name_mapping.target_name,
448 }
449 }
450
451 fn convert_availability(availability: decl::Availability) -> decl_next::Availability {
452 match availability {
453 decl::Availability::Required => decl_next::Availability::Required,
454 decl::Availability::Optional => decl_next::Availability::Optional,
455 decl::Availability::SameAsTarget => decl_next::Availability::SameAsTarget,
456 decl::Availability::Transitional => decl_next::Availability::Transitional,
457 }
458 }
459
460 fn convert_dependency_type(dependency_type: decl::DependencyType) -> decl_next::DependencyType {
461 match dependency_type {
462 decl::DependencyType::Strong => decl_next::DependencyType::Strong,
463 decl::DependencyType::Weak => decl_next::DependencyType::Weak,
464 }
465 }
466
467 fn convert_rights(rights: fidl_fuchsia_io::Operations) -> fidl_next_fuchsia_io::Operations {
468 fidl_next_fuchsia_io::Operations::from_bits_retain(rights.bits())
469 }
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475 use crate::{
476 DriverError, HealthAlert, HealthAlertReporter, HealthCheckError, HealthCheckInspect,
477 HealthCheckRequest, HealthCheckResponse, HealthCheckResult, HealthCondition, HealthStatus,
478 Node, NodeBuilder, ServiceInstance, ServiceOffer,
479 };
480 use diagnostics_assertions::assert_data_tree;
481 use fidl_next::{Request, Responder};
482 use fidl_next_fuchsia_examples as fexample;
483 use fidl_next_fuchsia_examples::echo::{EchoString, SendString};
484 use fuchsia_async as fasync;
485 use futures::StreamExt;
486 use futures::lock::Mutex;
487 use log::info;
488
489 struct EchoServer;
490
491 impl fexample::EchoServerHandler<zx::Channel> for EchoServer {
492 async fn echo_string(
493 &mut self,
494 request: Request<EchoString, zx::Channel>,
495 responder: Responder<EchoString, zx::Channel>,
496 ) {
497 info!("ECHO: {}", request.payload().value);
498 responder.respond("resp").await.unwrap();
499 }
500
501 async fn send_string(&mut self, _request: Request<SendString, zx::Channel>) {}
502 }
503
504 struct Service {
505 scope: fasync::ScopeHandle,
506 }
507
508 impl fexample::EchoServiceHandler for Service {
509 fn regular_echo(&self, server_end: NextServerEnd<fexample::Echo>) {
510 server_end.spawn_on(EchoServer, &self.scope);
511 }
512
513 fn reversed_echo(&self, _server_end: NextServerEnd<fexample::Echo>) {}
514 }
515
516 #[allow(dead_code)]
517 struct TestDriver {
518 node: Node,
519 scope: fasync::Scope,
520 tmp: Mutex<String>,
521 }
522
523 impl TestDriver {
524 async fn set_tmp(&self, resp: &str) {
525 let mut tmp = self.tmp.lock().await;
526 *tmp = resp.to_string();
527 }
528
529 async fn get_tmp(&self) -> String {
530 let tmp = self.tmp.lock().await;
531 tmp.to_string()
532 }
533 }
534
535 impl Driver for TestDriver {
536 const NAME: &'static str = "test-driver";
537
538 async fn start(mut context: crate::DriverContext) -> Result<Self, DriverError> {
539 let service_proxy: ServiceInstance<fexample::EchoService> =
540 context.incoming.service().connect_next()?;
541 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
542 service_proxy.regular_echo(server_end).unwrap();
543 let client = client_end.spawn();
544 let resp =
545 client.echo_string("echo from driver").await.map_err(|_| Status::IO_REFUSED)?;
546 assert_eq!("resp", resp.response.as_str());
547
548 let scope = fasync::Scope::new_with_name("test driver scope");
549 let mut outgoing = ServiceFs::new();
550 let offer = ServiceOffer::<fexample::EchoService>::new_next()
551 .add_named_next(&mut outgoing, "default", Service { scope: scope.to_handle() })
552 .build_zircon_offer_next();
553 context.serve_outgoing(&mut outgoing)?;
554 scope.spawn(outgoing.collect());
555
556 let node = context.take_node()?;
557 let child_node = NodeBuilder::new("transport-child")
558 .add_property("prop", "val")
559 .add_offer(offer)
560 .build();
561 node.add_child(child_node).await?;
562
563 info!("TestDriver started");
564 Ok(Self { node, scope, tmp: Mutex::new("NA".to_string()) })
565 }
566
567 async fn stop(&self) {
568 info!("TestDriver stopped. Tmp: '{}'", *self.tmp.lock().await);
569 }
570 }
571
572 #[fuchsia::test]
573 async fn test_basic() {
574 let scope = fasync::Scope::new_with_name("test scope");
575 let mut service_fs = ServiceFs::new();
576 let offer = ServiceOffer::<fexample::EchoService>::new_next()
577 .add_named_next(&mut service_fs, "default", Service { scope: scope.to_handle() })
578 .build_zircon_offer_next();
579 let mut harness = TestHarness::<TestDriver>::new()
580 .set_driver_incoming(service_fs)
581 .set_url("test_url")
582 .add_offer(offer);
583
584 let start_result = harness.start_driver().await;
585 let started_driver = start_result.expect("success");
586 let driver = started_driver.get_driver().expect("failed to get driver");
587 driver.set_tmp("my_temp_var").await;
588 assert_eq!("my_temp_var", driver.get_tmp().await);
589
590 let service_proxy: ServiceInstance<fexample::EchoService> =
591 started_driver.driver_outgoing().service().connect_next().unwrap();
592 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
593 service_proxy.regular_echo(server_end).unwrap();
594 let client = client_end.spawn();
595 let resp = client.echo_string("echo to driver").await.unwrap();
596 assert_eq!("resp", resp.response.as_str());
597 started_driver.stop_driver().await;
598 }
599
600 #[fuchsia::test]
601 async fn test_multiple_start_stop() {
602 let scope = fasync::Scope::new_with_name("test scope");
603 let mut service_fs = ServiceFs::new();
604 let offer = ServiceOffer::<fexample::EchoService>::new_next()
605 .add_named_next(&mut service_fs, "default", Service { scope: scope.to_handle() })
606 .build_zircon_offer_next();
607 let mut harness = TestHarness::<TestDriver>::new()
608 .set_driver_incoming(service_fs)
609 .set_url("test_url")
610 .add_offer(offer);
611
612 for i in 1..=3 {
613 let start_result = harness.start_driver().await;
614 let started_driver = start_result.expect("success");
615 let driver = started_driver.get_driver().expect("failed to get driver");
616 driver.set_tmp(format!("my_temp_var_{}", i).as_str()).await;
617 assert_eq!(format!("my_temp_var_{}", i), driver.get_tmp().await);
618
619 let service_proxy: ServiceInstance<fexample::EchoService> =
620 started_driver.driver_outgoing().service().connect_next().unwrap();
621 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
622 service_proxy.regular_echo(server_end).unwrap();
623 let client = client_end.spawn();
624 let resp = client.echo_string("echo to driver").await.unwrap();
625 assert_eq!("resp", resp.response.as_str());
626 started_driver.stop_driver().await;
627 }
628 }
629
630 #[fuchsia::test]
631 async fn test_no_start() {
632 let _harness = TestHarness::<TestDriver>::default();
633 }
634
635 #[fuchsia::test]
636 async fn test_start_fail() {
637 let mut harness = TestHarness::<TestDriver>::new();
638 let start_result = harness.start_driver().await;
639 assert_eq!(start_result.err(), Some(Status::IO_REFUSED));
640 }
641
642 struct StartArgsDriver {
644 node_name: Option<String>,
645 node_token: Option<zx::Event>,
646 power_element_token: Option<zx::Event>,
647 properties: Vec<(String, String, crate::PropertyValue)>,
648 }
649
650 impl Driver for StartArgsDriver {
651 const NAME: &'static str = "start-args-driver";
652
653 async fn start(mut context: crate::DriverContext) -> Result<Self, DriverError> {
654 let properties = context
655 .node_properties()
656 .map(|p| (p.parent.to_string(), p.key.to_string(), p.value))
657 .collect();
658 let driver = Self {
659 node_name: context.node_name().map(str::to_string),
660 node_token: context.take_node_token(),
661 power_element_token: context.take_power_element_token(),
662 properties,
663 };
664 assert!(context.take_node_token().is_none());
666 assert!(context.take_power_element_token().is_none());
667 Ok(driver)
668 }
669
670 async fn stop(&self) {}
671 }
672
673 fn koid(handle: &impl zx::AsHandleRef) -> zx::Koid {
674 handle.as_handle_ref().koid().unwrap()
675 }
676
677 #[fuchsia::test]
678 async fn test_start_args_accessors() {
679 let node_token = zx::Event::create();
680 let power_token = zx::Event::create();
681 let mut harness = TestHarness::<StartArgsDriver>::new()
682 .set_node_name("my-node")
683 .set_node_token(node_token.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap())
684 .set_power_element_token(power_token.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap())
685 .add_node_property("default", "fuchsia.BIND_PROTOCOL", 7u32)
686 .add_node_property("default", "name", "value")
687 .add_node_property("other-parent", "enabled", true)
688 .add_node_property(
689 "other-parent",
690 "kind",
691 crate::PropertyValue(fidl_fuchsia_driver_framework::NodePropertyValue::EnumValue(
692 "Kind.A".into(),
693 )),
694 );
695
696 let started_driver = harness.start_driver().await.expect("driver starts");
697 let driver = started_driver.get_driver().expect("driver");
698
699 assert_eq!(driver.node_name.as_deref(), Some("my-node"));
700 assert_eq!(driver.node_token.as_ref().map(koid), Some(koid(&node_token)));
701 assert_eq!(driver.power_element_token.as_ref().map(koid), Some(koid(&power_token)));
702
703 let as_strs: Vec<(&str, &str)> =
704 driver.properties.iter().map(|(p, k, _)| (p.as_str(), k.as_str())).collect();
705 assert_eq!(
706 as_strs,
707 [
708 ("default", "fuchsia.BIND_PROTOCOL"),
709 ("default", "name"),
710 ("other-parent", "enabled"),
711 ("other-parent", "kind"),
712 ]
713 );
714 assert_eq!(driver.properties[0].2.as_int(), Some(7));
715 assert_eq!(driver.properties[0].2.as_str(), None);
716 assert_eq!(driver.properties[1].2.as_str(), Some("value"));
717 assert_eq!(driver.properties[2].2.as_bool(), Some(true));
718 assert_eq!(driver.properties[3].2.as_enum(), Some("Kind.A"));
719 started_driver.stop_driver().await;
720 }
721
722 #[fuchsia::test]
723 async fn test_start_args_accessors_absent() {
724 let mut harness = TestHarness::<StartArgsDriver>::new();
725 let started_driver = harness.start_driver().await.expect("driver starts");
726 let driver = started_driver.get_driver().expect("driver");
727 assert!(driver.node_name.is_none());
728 assert!(driver.node_token.is_none());
729 assert!(driver.power_element_token.is_none());
730 assert!(driver.properties.is_empty());
731 started_driver.stop_driver().await;
732 }
733
734 #[fuchsia::test]
736 async fn test_legacy_power_element_args() {
737 let power_token = zx::Event::create();
738 let mut harness = TestHarness::<StartArgsDriver>::new().set_power_element_args(
739 fidl_fuchsia_driver_framework::PowerElementArgs {
740 token: Some(power_token.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
741 ..Default::default()
742 },
743 );
744 let started_driver = harness.start_driver().await.expect("driver starts");
745 let driver = started_driver.get_driver().expect("driver");
746 assert_eq!(driver.power_element_token.as_ref().map(koid), Some(koid(&power_token)));
747 started_driver.stop_driver().await;
748 }
749
750 struct DefaultHealthDriver;
752 impl Driver for DefaultHealthDriver {
753 const NAME: &str = "default_health_driver";
754 async fn start(_context: crate::DriverContext) -> Result<Self, crate::DriverError> {
755 Ok(Self)
756 }
757 async fn stop(&self) {}
758 }
759
760 #[fuchsia::test]
761 async fn test_dut_health_check_default() {
762 let mut harness = TestHarness::<DefaultHealthDriver>::new();
763 let started_driver = harness.start_driver().await.expect("success");
764
765 let ping_res = started_driver.health_check(HealthCheckRequest::Ping).await;
766 assert_eq!(ping_res, Ok(HealthCheckResponse::default()));
767 let fast_res = started_driver.health_check(HealthCheckRequest::Fast).await;
768 assert_eq!(fast_res, Err(HealthCheckError::NotSupported));
769
770 started_driver.stop_driver().await;
771 }
772
773 struct CustomHealthDriver {
776 inspector: fuchsia_inspect::Inspector,
777 health_inspect: HealthCheckInspect,
778 }
779 impl Driver for CustomHealthDriver {
780 const NAME: &str = "custom_health_driver";
781 async fn start(_context: crate::DriverContext) -> Result<Self, crate::DriverError> {
782 let inspector = fuchsia_inspect::Inspector::default();
783 let health_inspect = HealthCheckInspect::new(inspector.root());
784 Ok(Self { inspector, health_inspect })
785 }
786 async fn stop(&self) {}
787
788 async fn health_check(&self, request: HealthCheckRequest) -> HealthCheckResult {
789 let result = match request {
790 HealthCheckRequest::Fast => Ok(HealthCheckResponse {
791 conditions: Some(vec![HealthCondition {
792 status: Some(HealthStatus::Ok),
793 reason_code: Some("CUSTOM_OK".to_string()),
794 ..Default::default()
795 }]),
796 ..Default::default()
797 }),
798 _ => Err(HealthCheckError::NotSupported),
799 };
800 self.health_inspect.record(request, &result);
801 result
802 }
803 }
804
805 #[fuchsia::test]
806 async fn test_dut_health_check_custom() {
807 let mut harness = TestHarness::<CustomHealthDriver>::new();
808 let started_driver = harness.start_driver().await.expect("success");
809
810 let fast_res = started_driver.health_check(HealthCheckRequest::Fast).await;
811 let expected = HealthCheckResponse {
812 conditions: Some(vec![HealthCondition {
813 status: Some(HealthStatus::Ok),
814 reason_code: Some("CUSTOM_OK".to_string()),
815 ..Default::default()
816 }]),
817 ..Default::default()
818 };
819 assert_eq!(fast_res, Ok(expected));
820
821 let driver = started_driver.get_driver().expect("failed to get driver");
823 assert_data_tree!(driver.inspector, root: {
824 driver_health_check: {
825 level: "FAST",
826 status: "OK",
827 conditions: { CUSTOM_OK: { status: "OK" } },
828 },
829 });
830
831 let deep_res = started_driver.health_check(HealthCheckRequest::Deep).await;
833 assert_eq!(deep_res, Err(HealthCheckError::NotSupported));
834 assert_data_tree!(driver.inspector, root: {
835 driver_health_check: {
836 level: "FAST",
837 status: "OK",
838 conditions: { CUSTOM_OK: { status: "OK" } },
839 },
840 });
841
842 started_driver.stop_driver().await;
843 }
844
845 fn alert_with_reason(reason_code: &str) -> HealthCheckResponse {
846 HealthCheckResponse {
847 conditions: Some(vec![HealthCondition {
848 status: Some(HealthStatus::Unhealthy),
849 reason_code: Some(reason_code.to_string()),
850 ..Default::default()
851 }]),
852 ..Default::default()
853 }
854 }
855
856 struct AlertDriver {
858 reporter: HealthAlertReporter,
859 }
860 impl Driver for AlertDriver {
861 const NAME: &str = "alert_driver";
862 async fn start(context: crate::DriverContext) -> Result<Self, crate::DriverError> {
863 context.report_health_alert(HealthAlert::SelfFault(alert_with_reason("IN_START")));
864 Ok(Self { reporter: context.health_alert_reporter() })
865 }
866 async fn stop(&self) {}
867 }
868
869 #[fuchsia::test]
870 async fn test_dut_health_alert() {
871 let mut harness = TestHarness::<AlertDriver>::new();
872 let mut started_driver = harness.start_driver().await.expect("success");
873
874 let Some(HealthAlert::SelfFault(details)) = started_driver.next_health_alert().await else {
875 panic!("expected SelfFault");
876 };
877 assert_eq!(details.conditions.unwrap()[0].reason_code.as_deref(), Some("IN_START"));
878
879 let driver = started_driver.get_driver().expect("failed to get driver");
880 driver
881 .reporter
882 .report_health_alert(HealthAlert::DownstreamNeighborFault(alert_with_reason("LATER")));
883 let Some(HealthAlert::DownstreamNeighborFault(details)) =
884 started_driver.next_health_alert().await
885 else {
886 panic!("expected DownstreamNeighborFault");
887 };
888 assert_eq!(details.conditions.unwrap()[0].reason_code.as_deref(), Some("LATER"));
889
890 started_driver.stop_driver().await;
891 }
892}