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