1pub use crate::testing::dut::DriverUnderTest;
8use crate::testing::logsink_connector;
9use crate::testing::node::NodeManager;
10use crate::{Driver, Incoming};
11use anyhow::Result;
12use fdf::{AutoReleaseDispatcher, DispatcherBuilder, WeakDispatcher};
13use fdf_env::Environment;
14use fidl::endpoints::{ClientEnd, Proxy};
15use fidl_fuchsia_driver_framework::Offer;
16use fidl_next::{ClientEnd as NextClientEnd, ServerEnd as NextServerEnd};
17use fidl_next_fuchsia_component_runner::natural::ComponentNamespaceEntry;
18use fidl_next_fuchsia_driver_framework::DriverStartArgs;
19use fidl_next_fuchsia_driver_framework::natural::Offer as NextOffer;
20use fuchsia_component::directory::open_directory_async;
21use fuchsia_component::server::{ServiceFs, ServiceObj};
22use futures::StreamExt;
23use std::marker::PhantomData;
24use std::sync::{Arc, Weak, mpsc};
25use zx::{HandleBased, Status};
26use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
27
28pub struct TestHarness<D> {
30 fdf_env_environment: Arc<Environment>,
31 node_manager: Arc<NodeManager>,
32 driver: Option<fdf_env::Driver<u32>>,
33 dispatcher: AutoReleaseDispatcher,
34 driver_incoming_dir: ClientEnd<fio::DirectoryMarker>,
35 config_vmo: Option<zx::Vmo>,
36 url: Option<String>,
37 offers: Option<Vec<NextOffer>>,
38 scope: fasync::Scope,
39 _d: PhantomData<D>,
40}
41
42impl<D: Driver> Default for TestHarness<D> {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl<D: Driver> TestHarness<D> {
49 pub fn new() -> Self {
51 let scope = fasync::Scope::new();
52 let mut driver_incoming = ServiceFs::new();
53 let env = Arc::new(Environment::start(0).unwrap());
54 let node_manager = NodeManager::new();
55 driver_incoming.dir("svc").add_service_connector(logsink_connector);
56
57 let (driver_incoming_dir_client, driver_incoming_dir_server) = zx::Channel::create();
58 driver_incoming.serve_connection(driver_incoming_dir_server.into()).unwrap();
59 let driver_incoming_dir = driver_incoming_dir_client.into();
60
61 scope.spawn(async move {
62 driver_incoming.collect::<()>().await;
63 });
64
65 let driver_value_ptr = Box::into_raw(Box::new(0x1234_u32));
67 let driver = env.new_driver(driver_value_ptr);
68 let env_clone = env.clone();
69 let dispatcher_builder =
70 DispatcherBuilder::new().name("test_harness").shutdown_observer(move |dispatcher| {
71 assert!(!env_clone.dispatcher_has_queued_tasks(dispatcher.as_dispatcher_ref()));
74 });
75 let dispatcher =
76 AutoReleaseDispatcher::from(driver.new_dispatcher(dispatcher_builder).unwrap());
77 let driver = Some(driver);
78
79 Self {
80 fdf_env_environment: env,
81 node_manager,
82 driver,
83 dispatcher,
84 driver_incoming_dir,
85 config_vmo: None,
86 url: None,
87 offers: None,
88 scope,
89 _d: PhantomData,
90 }
91 }
92
93 pub fn set_driver_incoming(
95 mut self,
96 mut driver_incoming: ServiceFs<ServiceObj<'static, ()>>,
97 ) -> Self {
98 driver_incoming.dir("svc").add_service_connector(logsink_connector);
99
100 let (driver_incoming_dir_client, driver_incoming_dir_server) = zx::Channel::create();
101 driver_incoming.serve_connection(driver_incoming_dir_server.into()).unwrap();
102 let driver_incoming_dir = driver_incoming_dir_client.into();
103 self.scope.spawn(async move {
104 driver_incoming.collect::<()>().await;
105 });
106
107 self.driver_incoming_dir = driver_incoming_dir;
108 self
109 }
110
111 pub fn set_config(mut self, config: zx::Vmo) -> Self {
113 self.config_vmo = Some(config);
114 self
115 }
116
117 pub fn set_url(mut self, url: &str) -> Self {
119 self.url = Some(url.to_string());
120 self
121 }
122
123 pub fn add_offer(mut self, offer: Offer) -> Self {
125 self.offers.get_or_insert_default().push(convert::convert_df_offer(offer));
126 self
127 }
128
129 pub fn dispatcher(&self) -> WeakDispatcher {
131 WeakDispatcher::from(&self.dispatcher)
132 }
133
134 pub(crate) fn node_manager(&self) -> Weak<NodeManager> {
135 Arc::downgrade(&self.node_manager)
136 }
137
138 pub async fn start_driver(&mut self) -> Result<DriverUnderTest<'_, D>, Status> {
140 let (node_client, node_server) = zx::Channel::create();
141 let node_id = self.node_manager.create_root_node(node_server.into());
142
143 let (driver_outgoing_dir_client, driver_outgoing_dir_server) =
144 fidl::endpoints::create_endpoints();
145 let driver_outgoing = Incoming::from(driver_outgoing_dir_client);
146
147 let driver_incoming_svc =
148 open_directory_async(&self.driver_incoming_dir, "svc", fio::R_STAR_DIR).unwrap();
149
150 let start_args = DriverStartArgs {
151 node: Some(NextClientEnd::from_untyped(node_client)),
152 incoming: Some(vec![ComponentNamespaceEntry {
153 path: Some("/svc".to_string()),
154 directory: Some(NextClientEnd::from_untyped(
155 driver_incoming_svc.into_channel().unwrap().into(),
156 )),
157 }]),
158 outgoing_dir: Some(NextServerEnd::from_untyped(
159 driver_outgoing_dir_server.into_channel(),
160 )),
161 config: self
162 .config_vmo
163 .as_ref()
164 .and_then(|v| v.duplicate_handle(fidl::Rights::SAME_RIGHTS).ok()),
165 url: self.url.clone(),
166 node_offers: self.offers.clone(),
167 ..DriverStartArgs::default()
168 };
169
170 let mut driver =
171 DriverUnderTest::new(self, self.fdf_env_environment.clone(), driver_outgoing, node_id)
172 .await;
173 driver.start_driver(start_args).await?;
175 Ok(driver)
176 }
177}
178
179impl<D> Drop for TestHarness<D> {
180 fn drop(&mut self) {
181 let (shutdown_tx, shutdown_rx) = mpsc::channel();
182 self.driver.take().expect("driver").shutdown(move |driver_ref| {
183 let driver_value = unsafe { Box::from_raw(driver_ref.0 as *mut u32) };
185 assert_eq!(*driver_value, 0x1234);
186 shutdown_tx.send(()).unwrap();
187 });
188
189 shutdown_rx.recv().unwrap();
190
191 self.fdf_env_environment.destroy_all_dispatchers();
192 self.fdf_env_environment.reset();
193 }
194}
195
196mod convert {
197 use {
198 fidl_fuchsia_component_decl as decl, fidl_fuchsia_driver_framework as df,
199 fidl_next_fuchsia_component_decl as decl_next,
200 fidl_next_fuchsia_driver_framework as df_next,
201 };
202
203 pub fn convert_df_offer(offer: df::Offer) -> df_next::Offer {
204 match offer {
205 df::Offer::DictionaryOffer(o) => df_next::Offer::DictionaryOffer(convert_offer(o)),
206 df::Offer::ZirconTransport(o) => df_next::Offer::ZirconTransport(convert_offer(o)),
207 df::Offer::DriverTransport(o) => df_next::Offer::DriverTransport(convert_offer(o)),
208 df::Offer::__SourceBreaking { unknown_ordinal } => {
209 df_next::Offer::UnknownOrdinal_(unknown_ordinal)
210 }
211 }
212 }
213
214 fn convert_offer(offer: decl::Offer) -> decl_next::Offer {
215 match offer {
216 decl::Offer::Service(o) => decl_next::Offer::Service(decl_next::OfferService {
217 source: o.source.map(convert_ref),
218 source_name: o.source_name,
219 target: o.target.map(convert_ref),
220 target_name: o.target_name,
221 source_instance_filter: o.source_instance_filter,
222 renamed_instances: o
223 .renamed_instances
224 .map(|v| v.into_iter().map(convert_name_mapping).collect()),
225 availability: o.availability.map(convert_availability),
226 source_dictionary: o.source_dictionary,
227 dependency_type: o.dependency_type.map(convert_dependency_type),
228 }),
229 decl::Offer::Protocol(o) => decl_next::Offer::Protocol(decl_next::OfferProtocol {
230 source: o.source.map(convert_ref),
231 source_name: o.source_name,
232 target: o.target.map(convert_ref),
233 target_name: o.target_name,
234 dependency_type: o.dependency_type.map(convert_dependency_type),
235 availability: o.availability.map(convert_availability),
236 source_dictionary: o.source_dictionary,
237 }),
238 decl::Offer::Directory(o) => decl_next::Offer::Directory(decl_next::OfferDirectory {
239 source: o.source.map(convert_ref),
240 source_name: o.source_name,
241 target: o.target.map(convert_ref),
242 target_name: o.target_name,
243 availability: o.availability.map(convert_availability),
244 source_dictionary: o.source_dictionary,
245 dependency_type: o.dependency_type.map(convert_dependency_type),
246 rights: o.rights.map(convert_rights),
247 subdir: o.subdir,
248 }),
249 decl::Offer::Storage(o) => decl_next::Offer::Storage(decl_next::OfferStorage {
250 source_name: o.source_name,
251 source: o.source.map(convert_ref),
252 target: o.target.map(convert_ref),
253 target_name: o.target_name,
254 availability: o.availability.map(convert_availability),
255 }),
256 decl::Offer::Runner(o) => decl_next::Offer::Runner(decl_next::OfferRunner {
257 source: o.source.map(convert_ref),
258 source_name: o.source_name,
259 target: o.target.map(convert_ref),
260 target_name: o.target_name,
261 source_dictionary: o.source_dictionary,
262 }),
263 decl::Offer::Resolver(o) => decl_next::Offer::Resolver(decl_next::OfferResolver {
264 source: o.source.map(convert_ref),
265 source_name: o.source_name,
266 target: o.target.map(convert_ref),
267 target_name: o.target_name,
268 source_dictionary: o.source_dictionary,
269 }),
270 decl::Offer::EventStream(o) => {
271 decl_next::Offer::EventStream(decl_next::OfferEventStream {
272 source: o.source.map(convert_ref),
273 source_name: o.source_name,
274 scope: o.scope.map(|v| v.into_iter().map(convert_ref).collect()),
275 target: o.target.map(convert_ref),
276 target_name: o.target_name,
277 availability: o.availability.map(convert_availability),
278 })
279 }
280 decl::Offer::Dictionary(o) => {
281 decl_next::Offer::Dictionary(decl_next::OfferDictionary {
282 source: o.source.map(convert_ref),
283 source_name: o.source_name,
284 target: o.target.map(convert_ref),
285 target_name: o.target_name,
286 dependency_type: o.dependency_type.map(convert_dependency_type),
287 availability: o.availability.map(convert_availability),
288 source_dictionary: o.source_dictionary,
289 })
290 }
291 decl::Offer::Config(o) => decl_next::Offer::Config(decl_next::OfferConfiguration {
292 source: o.source.map(convert_ref),
293 source_name: o.source_name,
294 target: o.target.map(convert_ref),
295 target_name: o.target_name,
296 availability: o.availability.map(convert_availability),
297 source_dictionary: o.source_dictionary,
298 }),
299 decl::Offer::__SourceBreaking { unknown_ordinal } => {
300 decl_next::Offer::UnknownOrdinal_(unknown_ordinal)
301 }
302 }
303 }
304
305 fn convert_ref(ref_: decl::Ref) -> decl_next::Ref {
306 match ref_ {
307 decl::Ref::Parent(_) => decl_next::Ref::Parent(()),
308 decl::Ref::Self_(_) => decl_next::Ref::Self_(()),
309 decl::Ref::Child(child_ref) => decl_next::Ref::Child(decl_next::ChildRef {
310 name: child_ref.name,
311 collection: child_ref.collection,
312 }),
313 decl::Ref::Collection(collection_ref) => {
314 decl_next::Ref::Collection(decl_next::CollectionRef { name: collection_ref.name })
315 }
316 decl::Ref::Framework(_) => decl_next::Ref::Framework(()),
317 decl::Ref::Capability(capability_ref) => {
318 decl_next::Ref::Capability(decl_next::CapabilityRef { name: capability_ref.name })
319 }
320 decl::Ref::Debug(_) => decl_next::Ref::Debug(()),
321 decl::Ref::VoidType(_) => decl_next::Ref::VoidType(()),
322 decl::Ref::Environment(_) => decl_next::Ref::Environment(()),
323 decl::Ref::__SourceBreaking { unknown_ordinal } => {
324 decl_next::Ref::UnknownOrdinal_(unknown_ordinal)
325 }
326 }
327 }
328
329 fn convert_name_mapping(name_mapping: decl::NameMapping) -> decl_next::NameMapping {
330 fidl_next_fuchsia_component_decl::NameMapping {
331 source_name: name_mapping.source_name,
332 target_name: name_mapping.target_name,
333 }
334 }
335
336 fn convert_availability(availability: decl::Availability) -> decl_next::Availability {
337 match availability {
338 decl::Availability::Required => decl_next::Availability::Required,
339 decl::Availability::Optional => decl_next::Availability::Optional,
340 decl::Availability::SameAsTarget => decl_next::Availability::SameAsTarget,
341 decl::Availability::Transitional => decl_next::Availability::Transitional,
342 }
343 }
344
345 fn convert_dependency_type(dependency_type: decl::DependencyType) -> decl_next::DependencyType {
346 match dependency_type {
347 decl::DependencyType::Strong => decl_next::DependencyType::Strong,
348 decl::DependencyType::Weak => decl_next::DependencyType::Weak,
349 }
350 }
351
352 fn convert_rights(rights: fidl_fuchsia_io::Operations) -> fidl_next_fuchsia_io::Operations {
353 fidl_next_fuchsia_io::Operations::from_bits_retain(rights.bits())
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360 use crate::{Node, NodeBuilder, ServiceInstance, ServiceOffer};
361 use fidl_next::{Request, Responder};
362 use fidl_next_fuchsia_examples::echo::{EchoString, SendString};
363 use futures::StreamExt;
364 use futures::lock::Mutex;
365 use log::info;
366 use {fidl_next_fuchsia_examples as fexample, fuchsia_async as fasync};
367
368 struct EchoServer;
369
370 impl fexample::EchoServerHandler<zx::Channel> for EchoServer {
371 async fn echo_string(
372 &mut self,
373 request: Request<EchoString, zx::Channel>,
374 responder: Responder<EchoString, zx::Channel>,
375 ) {
376 info!("ECHO: {}", request.payload().value);
377 responder.respond("resp").await.unwrap();
378 }
379
380 async fn send_string(&mut self, _request: Request<SendString, zx::Channel>) {}
381 }
382
383 struct Service {
384 scope: fasync::ScopeHandle,
385 }
386
387 impl fexample::EchoServiceHandler for Service {
388 fn regular_echo(&self, server_end: NextServerEnd<fexample::Echo>) {
389 server_end.spawn_on(EchoServer, &self.scope);
390 }
391
392 fn reversed_echo(&self, _server_end: NextServerEnd<fexample::Echo>) {}
393 }
394
395 #[allow(dead_code)]
396 struct TestDriver {
397 node: Node,
398 scope: fasync::Scope,
399 tmp: Mutex<String>,
400 }
401
402 impl TestDriver {
403 async fn set_tmp(&self, resp: &str) {
404 let mut tmp = self.tmp.lock().await;
405 *tmp = resp.to_string();
406 }
407
408 async fn get_tmp(&self) -> String {
409 let tmp = self.tmp.lock().await;
410 tmp.to_string()
411 }
412 }
413
414 impl Driver for TestDriver {
415 const NAME: &'static str = "test-driver";
416
417 async fn start(mut context: crate::DriverContext) -> Result<Self, Status> {
418 let service_proxy: ServiceInstance<fexample::EchoService> =
419 context.incoming.service().connect_next()?;
420 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
421 service_proxy.regular_echo(server_end).unwrap();
422 let client = client_end.spawn();
423 let resp =
424 client.echo_string("echo from driver").await.map_err(|_| Status::IO_REFUSED)?;
425 assert_eq!("resp", resp.response.as_str());
426
427 let scope = fasync::Scope::new_with_name("test driver scope");
428 let mut outgoing = ServiceFs::new();
429 let offer = ServiceOffer::<fexample::EchoService>::new_next()
430 .add_named_next(&mut outgoing, "default", Service { scope: scope.to_handle() })
431 .build_zircon_offer_next();
432 context.serve_outgoing(&mut outgoing)?;
433 scope.spawn(outgoing.collect());
434
435 let node = context.take_node()?;
436 let child_node = NodeBuilder::new("transport-child")
437 .add_property("prop", "val")
438 .add_offer(offer)
439 .build();
440 node.add_child(child_node).await?;
441
442 info!("TestDriver started");
443 Ok(Self { node, scope, tmp: Mutex::new("NA".to_string()) })
444 }
445
446 async fn stop(&self) {
447 info!("TestDriver stopped. Tmp: '{}'", *self.tmp.lock().await);
448 }
449 }
450
451 #[fuchsia::test]
452 async fn test_basic() {
453 let scope = fasync::Scope::new_with_name("test scope");
454 let mut service_fs = ServiceFs::new();
455 let offer = ServiceOffer::<fexample::EchoService>::new_next()
456 .add_named_next(&mut service_fs, "default", Service { scope: scope.to_handle() })
457 .build_zircon_offer_next();
458 let mut harness = TestHarness::<TestDriver>::new()
459 .set_driver_incoming(service_fs)
460 .set_url("test_url")
461 .add_offer(offer);
462
463 let start_result = harness.start_driver().await;
464 let started_driver = start_result.expect("success");
465 let driver = started_driver.get_driver().expect("failed to get driver");
466 driver.set_tmp("my_temp_var").await;
467 assert_eq!("my_temp_var", driver.get_tmp().await);
468
469 let service_proxy: ServiceInstance<fexample::EchoService> =
470 started_driver.driver_outgoing().service().connect_next().unwrap();
471 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
472 service_proxy.regular_echo(server_end).unwrap();
473 let client = client_end.spawn();
474 let resp = client.echo_string("echo to driver").await.unwrap();
475 assert_eq!("resp", resp.response.as_str());
476 started_driver.stop_driver().await;
477 }
478
479 #[fuchsia::test]
480 async fn test_multiple_start_stop() {
481 let scope = fasync::Scope::new_with_name("test scope");
482 let mut service_fs = ServiceFs::new();
483 let offer = ServiceOffer::<fexample::EchoService>::new_next()
484 .add_named_next(&mut service_fs, "default", Service { scope: scope.to_handle() })
485 .build_zircon_offer_next();
486 let mut harness = TestHarness::<TestDriver>::new()
487 .set_driver_incoming(service_fs)
488 .set_url("test_url")
489 .add_offer(offer);
490
491 for i in 1..=3 {
492 let start_result = harness.start_driver().await;
493 let started_driver = start_result.expect("success");
494 let driver = started_driver.get_driver().expect("failed to get driver");
495 driver.set_tmp(format!("my_temp_var_{}", i).as_str()).await;
496 assert_eq!(format!("my_temp_var_{}", i), driver.get_tmp().await);
497
498 let service_proxy: ServiceInstance<fexample::EchoService> =
499 started_driver.driver_outgoing().service().connect_next().unwrap();
500 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
501 service_proxy.regular_echo(server_end).unwrap();
502 let client = client_end.spawn();
503 let resp = client.echo_string("echo to driver").await.unwrap();
504 assert_eq!("resp", resp.response.as_str());
505 started_driver.stop_driver().await;
506 }
507 }
508
509 #[fuchsia::test]
510 async fn test_no_start() {
511 let _harness = TestHarness::<TestDriver>::default();
512 }
513
514 #[fuchsia::test]
515 async fn test_start_fail() {
516 let mut harness = TestHarness::<TestDriver>::new();
517 let start_result = harness.start_driver().await;
518 assert_eq!(start_result.err(), Some(Status::IO_REFUSED));
519 }
520}