blob: f0a4045130bc90ae2c4908ffd76748768c00925e [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::macros::{DriverRegistration, make_driver_registration};
use crate::testing::get_driver_from_token;
use crate::testing::harness::TestHarness;
use crate::testing::node::NodeHandle;
use crate::{Driver, Incoming};
use fdf::{
AsyncDispatcher, AutoReleaseDispatcher, DispatcherBuilder, OnDispatcher, WeakDispatcher,
};
use fdf_env::Environment;
use fdf_fidl::DriverChannel;
use fidl_next::{Client as NextClient, ClientDispatcher, ClientEnd as NextClientEnd};
use fidl_next_fuchsia_driver_framework::{Driver as NextDriver, DriverStartArgs};
use futures::channel::oneshot;
use std::marker::PhantomData;
use std::sync::{Arc, mpsc};
use zx::Status;
/// This manages the driver under test's lifecycle.
pub struct DriverUnderTest<'a, D> {
driver_outgoing: Incoming,
driver: Option<fdf_env::Driver<u32>>,
dispatcher: AutoReleaseDispatcher,
registration: DriverRegistration,
token: usize,
client: NextClient<NextDriver, DriverChannel>,
client_exit_rx: Option<mpsc::Receiver<()>>,
started: bool,
harness: &'a mut TestHarness<D>,
node_id: usize,
_d: PhantomData<D>,
}
impl<D> Drop for DriverUnderTest<'_, D> {
fn drop(&mut self) {
if !self.started {
self.client_exit_rx.take().expect("exit rx").recv().unwrap();
}
assert!(
self.client_exit_rx.is_none(),
"DriverUnderTest's stop_driver must be called before letting it go out of scope."
);
let (shutdown_tx, shutdown_rx) = mpsc::channel();
let destroy_fn = self.registration.v1.destroy.unwrap();
let driver_token = self.token;
self.driver.take().expect("driver").shutdown(move |driver_ref| {
// SAFTEY: we created this through Box::into_raw below inside of new.
let driver_value = unsafe { Box::from_raw(driver_ref.0 as *mut u32) };
assert_eq!(*driver_value, 0x1337);
// SAFETY: We ensures that the client_exit_rx has been called, which means that
// the handle from initialize is dropped.
unsafe {
destroy_fn(driver_token as *mut _);
}
shutdown_tx.send(()).unwrap();
});
shutdown_rx.recv().unwrap();
}
}
impl<'a, D: Driver> DriverUnderTest<'a, D> {
pub(crate) async fn new(
harness: &'a mut TestHarness<D>,
fdf_env_environment: Arc<Environment>,
driver_outgoing: Incoming,
node_id: usize,
) -> Self {
// Leak this to a raw, we will reconstitue a Box inside drop.
let driver_value_ptr = Box::into_raw(Box::new(0x1337_u32));
let driver = fdf_env_environment.new_driver(driver_value_ptr);
let dispatcher_builder = DispatcherBuilder::new()
.name("driver_under_test")
.shutdown_observer(move |dispatcher| {
// We verify that the dispatcher has no tasks left queued in it,
// just because this is testing code.
assert!(
!fdf_env_environment
.dispatcher_has_queued_tasks(dispatcher.as_dispatcher_ref())
);
});
let registration = make_driver_registration::<D>();
let dispatcher =
AutoReleaseDispatcher::from(driver.new_dispatcher(dispatcher_builder).unwrap());
let (server_chan, client_chan) = fdf::Channel::<[fidl_next::Chunk]>::create();
let channel_handle = server_chan.into_driver_handle().into_raw().get();
let (client_exit_tx, client_exit_rx) = mpsc::channel();
let (token_tx, token_rx) = oneshot::channel();
let initialize_fn = registration.v1.initialize.unwrap();
dispatcher
.post_task_sync(move |status| {
assert_eq!(status, Status::OK);
// SAFETY: We know it's safe to call initialize from the initial dispatcher and we
// know channel_handle is non-zero.
token_tx.send(unsafe { initialize_fn(channel_handle) }.addr()).unwrap();
})
.unwrap();
let token = token_rx.await.unwrap();
let client_end: NextClientEnd<NextDriver, DriverChannel> =
NextClientEnd::from_untyped(DriverChannel::new(client_chan));
let client_dispatcher = ClientDispatcher::new(client_end);
let client = client_dispatcher.client();
WeakDispatcher::from(&dispatcher)
.spawn(async move {
// We have to manually run the client indefinitely until it returns a PEER_CLOSED.
// At that point the driver has closed its server which signifies it has
// completed its stop or has failed to start.
client_dispatcher.run_client().await.unwrap_err();
client_exit_tx.send(()).unwrap();
})
.unwrap();
Self {
driver_outgoing,
driver: Some(driver),
dispatcher,
registration,
token,
client,
client_exit_rx: Some(client_exit_rx),
started: false,
harness,
node_id,
_d: PhantomData,
}
}
pub(crate) async fn start_driver(&mut self, start_args: DriverStartArgs) -> Result<(), Status> {
let start_result = self.client.start(start_args).await.expect("start call success");
match start_result {
fidl_next::FlexibleResult::Ok(_) => {
self.started = true;
Ok(())
}
fidl_next::FlexibleResult::Err(e) => Err(Status::from_raw(e)),
fidl_next::FlexibleResult::FrameworkErr(e) => {
log::error!("DriverUnderTest::start failed with framework error: {:?}", e);
Err(Status::INTERNAL)
}
}
}
/// Allows the test to connect to capabilities that are provided by the driver through its
/// outgoing namespace.
pub fn driver_outgoing(&self) -> &Incoming {
&self.driver_outgoing
}
/// Returns a reference to the driver instance.
pub fn get_driver(&self) -> Option<&'_ D> {
unsafe {
// SAFETY: We know that the driver_token is valid and that the driver is of type T.
get_driver_from_token(self.token)
}
}
/// Gets the driver's initial dispatcher.
pub fn dispatcher(&self) -> WeakDispatcher {
WeakDispatcher::from(&self.dispatcher)
}
/// Gets the TestNode that the driver-under-test is bound to.
pub fn node(&self) -> NodeHandle {
NodeHandle::new(self.harness.node_manager(), self.node_id)
}
/// Get a reference to the harness that started the driver.
pub fn harness(&self) -> &'_ TestHarness<D> {
self.harness
}
/// Stop the driver.
pub async fn stop_driver(mut self) {
// We should only send a stop request if the driver started successfully.
if self.started {
// Sometimes the channel closes earlier than we get the stop result.
let _stop_res = self.client.stop().await;
self.client_exit_rx.take().expect("exit rx").recv().unwrap();
}
}
}