Skip to main content

fdf_component/testing/
dut.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
5use crate::macros::{DriverRegistration, make_driver_registration};
6use crate::testing::get_driver_from_token;
7use crate::testing::harness::TestHarness;
8use crate::testing::node::NodeHandle;
9use crate::{Driver, Incoming};
10use fdf::{AsAsyncDispatcherRef, AsyncDispatcher, DispatcherBuilder, OnDispatcher};
11use fdf_env::Environment;
12use fdf_fidl::DriverChannel;
13use fidl_next::{Client as NextClient, ClientDispatcher, ClientEnd as NextClientEnd};
14use fidl_next_fuchsia_driver_framework::{
15    Driver as NextDriver, DriverClientHandler, DriverStartArgs, HealthAlert, HealthCheckError,
16    HealthCheckRequest, HealthCheckResponse, driver,
17};
18use fuchsia_async as fasync;
19use futures::channel::oneshot;
20use std::marker::PhantomData;
21use std::sync::{Arc, mpsc};
22use zx::Status;
23
24/// This manages the driver under test's lifecycle.
25pub struct DriverUnderTest<'a, D> {
26    driver_outgoing: Incoming,
27    driver: Option<fdf_env::Driver<u32>>,
28    dispatcher: AsyncDispatcher,
29    registration: DriverRegistration,
30    token: usize,
31    client: NextClient<NextDriver, DriverChannel>,
32    alert_rx: futures::channel::mpsc::Receiver<HealthAlert>,
33    client_exit_rx: Option<mpsc::Receiver<()>>,
34    started: bool,
35    harness: &'a mut TestHarness<D>,
36    node_id: usize,
37    _d: PhantomData<D>,
38}
39
40/// Collects the `OnHealthAlert` events the driver under test emits.
41#[derive(Debug)]
42struct DutClientHandler {
43    alert_tx: futures::channel::mpsc::Sender<HealthAlert>,
44}
45
46impl DriverClientHandler for DutClientHandler {
47    async fn on_health_alert(
48        &mut self,
49        request: fidl_next::Request<driver::OnHealthAlert, DriverChannel>,
50    ) {
51        // Tests that never read alerts must not stall the driver; drop on overflow.
52        let _ = self.alert_tx.try_send(request.payload().alert);
53    }
54}
55
56impl<D> Drop for DriverUnderTest<'_, D> {
57    fn drop(&mut self) {
58        if !self.started {
59            self.client_exit_rx.take().expect("exit rx").recv().unwrap();
60        }
61        assert!(
62            self.client_exit_rx.is_none(),
63            "DriverUnderTest's stop_driver must be called before letting it go out of scope."
64        );
65
66        let (shutdown_tx, shutdown_rx) = mpsc::channel();
67        let destroy_fn = self.registration.v1.destroy.unwrap();
68        let driver_token = self.token;
69        self.driver.take().expect("driver").shutdown(move |driver_ref| {
70            // SAFETY: we created this through Box::into_raw below inside of new.
71            let driver_value = unsafe { Box::from_raw(driver_ref.0 as *mut u32) };
72            assert_eq!(*driver_value, 0x1337);
73
74            // SAFETY: We ensures that the client_exit_rx has been called, which means that
75            // the handle from initialize is dropped.
76            unsafe {
77                destroy_fn(driver_token as *mut _);
78            }
79
80            shutdown_tx.send(()).unwrap();
81        });
82
83        shutdown_rx.recv().unwrap();
84    }
85}
86
87impl<'a, D: Driver> DriverUnderTest<'a, D> {
88    pub(crate) async fn new(
89        harness: &'a mut TestHarness<D>,
90        fdf_env_environment: Arc<Environment>,
91        driver_outgoing: Incoming,
92        node_id: usize,
93    ) -> Self {
94        // Leak this to a raw, we will reconstitue a Box inside drop.
95        let driver_value_ptr = Box::into_raw(Box::new(0x1337_u32));
96
97        let driver = fdf_env_environment.new_driver(driver_value_ptr);
98        let dispatcher_builder = DispatcherBuilder::new()
99            .name("driver_under_test")
100            .shutdown_observer(move |dispatcher| {
101                // We verify that the dispatcher has no tasks left queued in it,
102                // just because this is testing code.
103                assert!(
104                    !fdf_env_environment
105                        .dispatcher_has_queued_tasks(dispatcher.as_dispatcher_ref())
106                );
107            });
108
109        let registration = make_driver_registration::<D>();
110        let dispatcher =
111            AsyncDispatcher::new(&driver.new_dispatcher(dispatcher_builder).unwrap().release());
112        let (server_chan, client_chan) = fdf::Channel::<[fidl_next::Chunk]>::create();
113        let channel_handle = server_chan.into_driver_handle().into_raw().get();
114        let (client_exit_tx, client_exit_rx) = mpsc::channel();
115        let (alert_tx, alert_rx) = futures::channel::mpsc::channel(64);
116        let (token_tx, token_rx) = oneshot::channel();
117        let initialize_fn = registration.v1.initialize.unwrap();
118        dispatcher
119            .post_task_sync(move |status| {
120                assert_eq!(status, Ok(()));
121                // SAFETY: We know it's safe to call initialize from the initial dispatcher and we
122                // know channel_handle is non-zero.
123                token_tx.send(unsafe { initialize_fn(channel_handle) }.addr()).unwrap();
124            })
125            .unwrap();
126        let token = token_rx.await.unwrap();
127
128        let client_end: NextClientEnd<NextDriver, DriverChannel> =
129            NextClientEnd::from_untyped(DriverChannel::new(client_chan));
130        let client_dispatcher = ClientDispatcher::new(client_end);
131        let client = client_dispatcher.client();
132        dispatcher.spawn(async move {
133            // We have to manually run the client indefinitely until it returns a PEER_CLOSED.
134            // At that point the driver has closed its server which signifies it has
135            // completed its stop or has failed to start.
136            client_dispatcher.run(DutClientHandler { alert_tx }).await.unwrap_err();
137            client_exit_tx.send(()).unwrap();
138        });
139
140        Self {
141            driver_outgoing,
142            driver: Some(driver),
143            dispatcher,
144            registration,
145            token,
146            client,
147            alert_rx,
148            client_exit_rx: Some(client_exit_rx),
149            started: false,
150            harness,
151            node_id,
152            _d: PhantomData,
153        }
154    }
155
156    pub(crate) async fn start_driver(&mut self, start_args: DriverStartArgs) -> Result<(), Status> {
157        self.client.start(start_args).await.expect("start call success")?;
158        self.started = true;
159        Ok(())
160    }
161
162    /// Allows the test to connect to capabilities that are provided by the driver through its
163    /// outgoing namespace.
164    pub fn driver_outgoing(&self) -> &Incoming {
165        &self.driver_outgoing
166    }
167
168    /// Returns a reference to the driver instance.
169    pub fn get_driver(&self) -> Option<&'_ D> {
170        unsafe {
171            // SAFETY: We know that the driver_token is valid and that the driver is of type T.
172            get_driver_from_token(self.token)
173        }
174    }
175
176    /// Gets the driver's initial dispatcher.
177    pub fn dispatcher(&self) -> AsyncDispatcher {
178        self.dispatcher.clone()
179    }
180
181    /// Gets the TestNode that the driver-under-test is bound to.
182    pub fn node(&self) -> NodeHandle {
183        NodeHandle::new(self.harness.node_manager(), self.node_id)
184    }
185
186    /// Get a reference to the harness that started the driver.
187    pub fn harness(&self) -> &'_ TestHarness<D> {
188        self.harness
189    }
190
191    /// Sends a `HealthCheck` request over the driver channel to the driver under test, as the
192    /// driver host would, and returns the driver's reply.
193    pub async fn health_check(
194        &self,
195        request: HealthCheckRequest,
196    ) -> Result<HealthCheckResponse, HealthCheckError> {
197        self.client.health_check(request).await.expect("health_check FIDL call success")
198    }
199
200    /// Waits for the next `OnHealthAlert` event emitted by the driver under test. Returns `None`
201    /// once the driver has closed its channel.
202    pub async fn next_health_alert(&mut self) -> Option<HealthAlert> {
203        futures::StreamExt::next(&mut self.alert_rx).await
204    }
205
206    /// Stop the driver.
207    pub async fn stop_driver(mut self) {
208        // We should only send a stop request if the driver started successfully.
209        if self.started {
210            // Sometimes the channel closes earlier than we get the stop result.
211            let _stop_res = self.client.stop().await;
212            let client_exit_rx = self.client_exit_rx.take().expect("exit rx");
213            fasync::unblock(move || client_exit_rx.recv().unwrap()).await;
214        }
215    }
216}