Skip to main content

fdf_component/testing/
harness.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! The harness provides a way to spin up drivers for unit testing.
6
7pub 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
30/// The main test harness for running a driver unit test.
31pub 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    /// Creates a new `TestHarness` without a customized driver incoming ServiceFs.
56    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        // Leak this to a raw, we will reconstitue a Box inside drop.
72        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                // We verify that the dispatcher has no tasks left queued in it,
78                // just because this is testing code.
79                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    /// Sets the driver incoming ServiceFs. Consumes and returns self to allow chaining.
104    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    /// Sets the configuration vmo for the driver. Consumes and returns self to allow chaining.
122    pub fn set_config(mut self, config: zx::Vmo) -> Self {
123        self.config_vmo = Some(config);
124        self
125    }
126
127    /// Sets the url for the driver. Consumes and returns self to allow chaining.
128    pub fn set_url(mut self, url: &str) -> Self {
129        self.url = Some(url.to_string());
130        self
131    }
132
133    /// Sets the node name for the driver. Consumes and returns self to allow chaining.
134    pub fn set_node_name(mut self, name: impl Into<String>) -> Self {
135        self.node_name = Some(name.into());
136        self
137    }
138
139    /// Adds an offer to the driver's start args. Consumes and returns self to allow chaining.
140    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    /// Sets the power element args for the driver. Accepts both
146    /// `fidl_next_fuchsia_driver_framework::PowerElementArgs` and, for drivers that haven't
147    /// migrated yet, the legacy `fidl_fuchsia_driver_framework::PowerElementArgs`. Consumes and
148    /// returns self to allow chaining.
149    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    /// Sets the node token for the driver. Consumes and returns self to allow chaining.
155    pub fn set_node_token(mut self, token: zx::Event) -> Self {
156        self.node_token = Some(token);
157        self
158    }
159
160    /// Sets the power element dependency token for the driver, as returned by
161    /// [`crate::DriverContext::take_power_element_token`]. This only populates the `token` of the
162    /// power element args; use [`Self::set_power_element_args`] to also provide the power broker
163    /// endpoints. Consumes and returns self to allow chaining.
164    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    /// Adds a property of the parent node named `parent` to the driver's start args, as reported
170    /// by [`crate::DriverContext::node_properties`]. Drivers bound to a single node see their
171    /// properties under the parent name `default`. Consumes and returns self to allow chaining.
172    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    /// Gets a driver dispatcher that can be used to run test side driver transport client/servers.
193    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    /// Starts the driver under test.
202    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        // If the driver fails to start we will drop it here and allow it to run the destroy hook.
245        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            // SAFETY: we created this through Box::into_raw below inside of new.
255            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
267/// Power element args accepted by [`TestHarness::set_power_element_args`].
268///
269/// Implemented for `fidl_next_fuchsia_driver_framework::PowerElementArgs`, which the harness
270/// stores, and for the legacy `fidl_fuchsia_driver_framework::PowerElementArgs` so that drivers
271/// still on the legacy bindings keep compiling while they migrate.
272pub trait IntoPowerElementArgs {
273    /// Converts the args into their `fidl_next` representation.
274    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    /// Records what the `DriverContext` start-argument accessors returned.
643    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            // Taking is one-shot.
665            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    // Drivers that are still on the legacy bindings pass legacy power element args.
735    #[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    /// Leaves health at the SDK defaults.
751    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    /// Answers FAST checks with a custom condition and records every result into an inspector it
774    /// keeps for the test to read, the way a real driver would with the inspector it publishes.
775    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        // The result was recorded into the inspector the driver owns.
822        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        // A NOT_SUPPORTED error on DEEP does not overwrite the informative FAST record.
832        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    /// Reports one alert during `start` and keeps its reporter to report more later.
857    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}