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