Skip to main content

fuchsia_driver_test/
builder.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 anyhow::{Context as _, Result};
6use fidl::HandleBased;
7
8use cm_rust::FidlIntoNative;
9use fidl_fuchsia_component_decl as fdecl;
10use fidl_fuchsia_component_test as ftest;
11use fidl_fuchsia_driver_development as fdd;
12use fidl_fuchsia_driver_test as fdt;
13use fidl_fuchsia_io as fio;
14use flyweights::FlyStr;
15use fuchsia_async as fasync;
16use fuchsia_component::server::ServiceFs;
17use fuchsia_component_test::{
18    Capability, ChildOptions, ChildRef, CollectionRef, LocalComponentHandles, RealmBuilder,
19    RealmInstance, Ref, Route,
20};
21use futures::{StreamExt, TryStreamExt};
22use std::sync::Arc;
23use zx::AsHandleRef;
24
25fn clone(
26    dir: &fio::DirectoryProxy,
27) -> Result<fidl::endpoints::ClientEnd<fio::DirectoryMarker>, fidl::Error> {
28    let (client_end, server_end) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
29    dir.clone(fidl::endpoints::ServerEnd::new(server_end.into_channel()))?;
30    Ok(client_end)
31}
32
33async fn internal_serve(
34    stream: fidl_fuchsia_driver_test::InternalRequestStream,
35    test_pkg_dir: Arc<fio::DirectoryProxy>,
36    test_resolution_context: Arc<Option<fidl_fuchsia_component_resolution::Context>>,
37    boot_dir: Arc<Option<fio::DirectoryProxy>>,
38    boot_driver_components: Arc<Option<Vec<String>>>,
39) {
40    stream
41        .try_for_each_concurrent(None, |request| {
42            let test_pkg_dir = test_pkg_dir.clone();
43            let test_resolution_context = test_resolution_context.clone();
44            let boot_dir = boot_dir.clone();
45            let boot_driver_components = boot_driver_components.clone();
46            async move {
47                match request {
48                    fidl_fuchsia_driver_test::InternalRequest::GetTestPackage { responder } => {
49                        let cloned = clone(test_pkg_dir.as_ref())?;
50                        responder.send(Ok(Some(cloned)))?;
51                    }
52                    fidl_fuchsia_driver_test::InternalRequest::GetTestResolutionContext {
53                        responder,
54                    } => responder.send(Ok(test_resolution_context.as_ref().as_ref()))?,
55                    fidl_fuchsia_driver_test::InternalRequest::GetBootDirectory { responder } => {
56                        match boot_dir.as_ref() {
57                            Some(boot_dir) => {
58                                let cloned = clone(boot_dir)?;
59                                responder.send(Ok(Some(cloned)))?;
60                            }
61                            None => responder.send(Ok(None))?,
62                        }
63                    }
64                    fidl_fuchsia_driver_test::InternalRequest::GetBootDriverOverrides {
65                        responder,
66                    } => responder.send(Ok(boot_driver_components
67                        .as_ref()
68                        .clone()
69                        .unwrap_or(vec![])
70                        .as_slice()))?,
71                };
72                Ok(())
73            }
74        })
75        .await
76        .expect("fuchsia.driver.test.Internal failed.");
77}
78
79async fn resource_provider_serve(
80    stream: fidl_fuchsia_driver_test::ResourceProviderRequestStream,
81    devicetree: Arc<Option<zx::Vmo>>,
82) {
83    stream
84        .try_for_each_concurrent(None, |request| {
85            let devicetree = devicetree.clone();
86            async move {
87                match request {
88                    fidl_fuchsia_driver_test::ResourceProviderRequest::GetDeviceTree {
89                        responder,
90                    } => {
91                        if let Some(vmo) = devicetree.as_ref().as_ref().map(|d| {
92                            d.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate")
93                        }) {
94                            responder.send(Ok(vmo))?;
95                        } else {
96                            responder.send(Err(zx::Status::NOT_FOUND.into_raw()))?;
97                        }
98                    }
99                };
100                Ok(())
101            }
102        })
103        .await
104        .expect("fuchsia.driver.test.Internal failed.");
105}
106
107async fn run_internal_server(
108    handles: LocalComponentHandles,
109    test_pkg_dir: Arc<fio::DirectoryProxy>,
110    test_resolution_context: Arc<Option<fidl_fuchsia_component_resolution::Context>>,
111    boot_dir: Arc<Option<fio::DirectoryProxy>>,
112    boot_driver_components: Arc<Option<Vec<String>>>,
113    devicetree: Arc<Option<zx::Vmo>>,
114) -> Result<()> {
115    let mut fs = ServiceFs::new();
116
117    fs.dir("svc").add_fidl_service(
118        move |stream: fidl_fuchsia_driver_test::InternalRequestStream| {
119            fasync::Task::spawn(internal_serve(
120                stream,
121                test_pkg_dir.clone(),
122                test_resolution_context.clone(),
123                boot_dir.clone(),
124                boot_driver_components.clone(),
125            ))
126            .detach();
127        },
128    );
129    fs.dir("svc").add_fidl_service(
130        move |stream: fidl_fuchsia_driver_test::ResourceProviderRequestStream| {
131            fasync::Task::spawn(resource_provider_serve(stream, devicetree.clone())).detach();
132        },
133    );
134    fs.serve_connection(handles.outgoing_dir)?;
135    fs.collect::<()>().await;
136    Ok(())
137}
138
139// Basic equality check of just the name.
140fn capabilities_eq_name(a: &ftest::Capability, b: &ftest::Capability) -> bool {
141    match (a, b) {
142        (ftest::Capability::Protocol(a), ftest::Capability::Protocol(b)) => a.name == b.name,
143        (ftest::Capability::Directory(a), ftest::Capability::Directory(b)) => a.name == b.name,
144        (ftest::Capability::Storage(a), ftest::Capability::Storage(b)) => a.name == b.name,
145        (ftest::Capability::Service(a), ftest::Capability::Service(b)) => a.name == b.name,
146        (ftest::Capability::EventStream(a), ftest::Capability::EventStream(b)) => a.name == b.name,
147        (ftest::Capability::Config(a), ftest::Capability::Config(b)) => a.name == b.name,
148        (ftest::Capability::Dictionary(a), ftest::Capability::Dictionary(b)) => a.name == b.name,
149        (ftest::Capability::Resolver(a), ftest::Capability::Resolver(b)) => a.name == b.name,
150        (ftest::Capability::Runner(a), ftest::Capability::Runner(b)) => a.name == b.name,
151        _ => false,
152    }
153}
154
155#[derive(Debug, Clone, Default)]
156pub struct Options {
157    using_subpackage: Option<bool>,
158    driver_offers: Option<(Ref, Vec<ftest::Capability>)>,
159    driver_exposes: Option<Vec<ftest::Capability>>,
160    extra_realm_capabilities: Vec<(ftest::Capability, Ref)>,
161}
162
163impl Options {
164    pub fn new() -> Self {
165        Self::default()
166    }
167
168    pub fn using_subpackage(mut self, using_subpackage: bool) -> Self {
169        self.using_subpackage = Some(using_subpackage);
170        self
171    }
172
173    pub fn driver_offers(mut self, provider: Ref, offers: Vec<ftest::Capability>) -> Self {
174        self.driver_offers = Some((provider, offers));
175        self
176    }
177
178    pub fn driver_exposes(mut self, exposes: Vec<ftest::Capability>) -> Self {
179        self.driver_exposes = Some(exposes);
180        self
181    }
182
183    pub fn add_extra_realm_capability(
184        mut self,
185        capability: ftest::Capability,
186        from: impl Into<Ref>,
187    ) -> Self {
188        self.extra_realm_capabilities.push((capability, from.into()));
189        self
190    }
191}
192
193#[async_trait::async_trait]
194pub trait DriverTestRealmBuilder {
195    /// Set up the driver test realm.
196    /// This version is a collapsed version compared to the previous version which used a two layer
197    /// realm builder setup.
198    async fn driver_test_realm_setup(
199        &self,
200        options: Options,
201        args: fdt::RealmArgs,
202    ) -> Result<&Self>;
203}
204
205#[async_trait::async_trait]
206impl DriverTestRealmBuilder for RealmBuilder {
207    async fn driver_test_realm_setup(
208        &self,
209        options: Options,
210        args: fdt::RealmArgs,
211    ) -> Result<&Self> {
212        let manifest_provider =
213            fuchsia_component::client::connect_to_protocol::<fdt::ManifestProviderMarker>()?;
214        let stream = manifest_provider
215            .get_manifest(&fdt::GetManifestRequest {
216                using_subpackage: options.using_subpackage,
217                ..Default::default()
218            })
219            .await?
220            .expect("manifest stream");
221
222        let mut manifest: Vec<u8> = vec![];
223        loop {
224            let mut read = stream.read_to_vec(
225                zx::StreamReadOptions::empty(),
226                fidl_fuchsia_io::MAX_TRANSFER_SIZE as usize,
227            )?;
228            if read.is_empty() {
229                break;
230            }
231            manifest.append(&mut read);
232        }
233
234        let component = fidl::unpersist::<fdecl::Component>(manifest.as_slice())
235            .context("unpersisting the manifest vector")?;
236
237        // Keep the rust and c++ realm_builder setups in sync.
238        // LINT.IfChange
239        let realm = self
240            .add_child_realm_from_decl(
241                "driver_test_realm",
242                component.fidl_into_native(),
243                ChildOptions::new(),
244            )
245            .await?;
246
247        // From the test root into the dtr.
248        self.add_route(
249            Route::new()
250                .capability(Capability::protocol_by_name("fuchsia.diagnostics.ArchiveAccessor"))
251                .from(Ref::parent())
252                .to(&realm),
253        )
254        .await?;
255
256        let dtr_support: ChildRef = "dtr_support".into();
257        let fake_resolver: ChildRef = "fake_resolver".into();
258        let driver_manager: ChildRef = "driver_manager".into();
259        let driver_index: ChildRef = "driver_index".into();
260
261        let boot_drivers: CollectionRef = "boot-drivers".into();
262        let base_drivers: CollectionRef = "base-drivers".into();
263        let full_drivers: CollectionRef = "full-drivers".into();
264
265        let devicetree = Arc::new(args.devicetree);
266
267        // Get the test component information.
268        let test_component = if let Some(test_component) = args.test_component {
269            test_component
270        } else {
271            let realm = fuchsia_component::client::connect_to_protocol::<
272                fidl_fuchsia_component::RealmMarker,
273            >()?;
274
275            realm.get_resolved_info().await.unwrap().unwrap()
276        };
277
278        let test_resolution_context = Arc::new(test_component.resolution_context);
279        let test_pkg_dir = Arc::new(
280            test_component.package.expect("a pkg").directory.expect("a directory").into_proxy(),
281        );
282
283        let boot_dir = Arc::new(match args.boot {
284            Some(boot_dir) => {
285                if !boot_dir.as_handle_ref().is_invalid() {
286                    Some(boot_dir.into_proxy())
287                } else {
288                    None
289                }
290            }
291            None => None,
292        });
293        let boot_driver_components = Arc::new(args.boot_driver_components);
294
295        // Route the resolvers from the test root.
296        self.add_route(
297            Route::new()
298                .capability(
299                    Capability::protocol_by_name("fuchsia.component.resolution.Resolver-hermetic")
300                        .optional(),
301                )
302                .capability(
303                    Capability::protocol_by_name("fuchsia.pkg.PackageResolver-hermetic").optional(),
304                )
305                .from(Ref::parent())
306                .to(&realm),
307        )
308        .await?;
309
310        // Setup the local Internal protocol server child.
311        let driver_test_internal = realm
312            .add_local_child(
313                "driver_test_internal",
314                move |handles| {
315                    let test_pkg_dir = test_pkg_dir.clone();
316                    let test_resolution_context = test_resolution_context.clone();
317                    let boot_dir = boot_dir.clone();
318                    let boot_driver_components = boot_driver_components.clone();
319                    let devicetree = devicetree.clone();
320
321                    Box::pin(run_internal_server(
322                        handles,
323                        test_pkg_dir,
324                        test_resolution_context,
325                        boot_dir,
326                        boot_driver_components,
327                        devicetree,
328                    ))
329                },
330                ChildOptions::new(),
331            )
332            .await?;
333
334        // These are capabilities that are routed from void by default but can be provided manually
335        // from the user through extra_realm_capabilities.
336        let mut tunnel_boot_items = false;
337        let mut voided_offers: Vec<ftest::Capability> = vec![
338            Capability::protocol_by_name("fuchsia.tracing.provider.Registry").optional().into(),
339            Capability::protocol_by_name("fuchsia.boot.WriteOnlyLog").optional().into(),
340            Capability::protocol_by_name("fuchsia.scheduler.RoleManager").optional().into(),
341            Capability::protocol_by_name("fuchsia.boot.Items").optional().into(),
342            Capability::protocol_by_name("fuchsia.kernel.IommuResource").optional().into(),
343            Capability::protocol_by_name("fuchsia.diagnostics.LogFlusher").optional().into(),
344            Capability::protocol_by_name("fuchsia.kernel.MexecResource").optional().into(),
345            Capability::protocol_by_name("fuchsia.kernel.PowerResource").optional().into(),
346        ];
347        for (capability, from) in options.extra_realm_capabilities {
348            // Remove the default voiding for any user provided capabilities.
349            voided_offers.retain(|voided| !capabilities_eq_name(voided, &capability));
350
351            if from != Ref::void()
352                && capabilities_eq_name(
353                    &Capability::protocol_by_name("fuchsia.boot.Items").into(),
354                    &capability,
355                )
356            {
357                tunnel_boot_items = true;
358            }
359
360            self.add_route(Route::new().capability(capability).from(from).to(&realm)).await?;
361        }
362
363        // Set the default void route for remaining voided offers.
364        for voided in voided_offers {
365            self.add_route(Route::new().capability(voided).from(Ref::void()).to(&realm)).await?;
366        }
367
368        // Provide offers from the driver_offers, if the test provides one, to the driver
369        // collections.
370        if args.dtr_offers.is_some() {
371            panic!("Please use |Options::driver_offers| instead of dtr_offers.")
372        }
373        if let Some((provider, offers)) = options.driver_offers {
374            for offer in offers {
375                self.add_route(
376                    Route::new().capability(offer.clone()).from(provider.clone()).to(&realm),
377                )
378                .await?;
379                realm
380                    .add_route(
381                        Route::new()
382                            .capability(offer)
383                            .from(Ref::parent())
384                            .to(&boot_drivers)
385                            .to(&base_drivers)
386                            .to(&full_drivers),
387                    )
388                    .await?;
389            }
390        }
391
392        // Provide exposes from the driver collections to the test.
393        if args.dtr_exposes.is_some() {
394            panic!("Please use |Options::driver_exposes| instead of dtr_exposes.")
395        }
396        if let Some(exposes) = options.driver_exposes {
397            for expose in exposes {
398                realm
399                    .add_route(
400                        Route::new()
401                            .capability(expose.clone())
402                            .from(&boot_drivers)
403                            .to(Ref::parent()),
404                    )
405                    .await?;
406                realm
407                    .add_route(
408                        Route::new()
409                            .capability(expose.clone())
410                            .from(&base_drivers)
411                            .to(Ref::parent()),
412                    )
413                    .await?;
414                realm
415                    .add_route(
416                        Route::new()
417                            .capability(expose.clone())
418                            .from(&full_drivers)
419                            .to(Ref::parent()),
420                    )
421                    .await?;
422
423                self.add_route(Route::new().capability(expose).from(&realm).to(Ref::parent()))
424                    .await?;
425            }
426        }
427
428        // Setup the driver test resource provider.
429        realm
430            .add_route(
431                Route::new()
432                    .capability(Capability::protocol_by_name(
433                        "fuchsia.driver.test.ResourceProvider",
434                    ))
435                    .from(&driver_test_internal)
436                    .to(&dtr_support),
437            )
438            .await?;
439
440        // Setup various basic config capabilities.
441        realm
442            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
443                name: "fuchsia.driver.testrealm.TunnelBootItems".parse()?,
444                value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(
445                    tunnel_boot_items,
446                )),
447            }))
448            .await?;
449
450        realm
451            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
452                name: "fuchsia.driver.testrealm.BoardName".parse()?,
453                value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
454                    args.board_name.unwrap_or_default().into(),
455                )),
456            }))
457            .await?;
458
459        realm
460            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
461                name: "fuchsia.driver.testrealm.PlatformVid".parse()?,
462                value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
463                    args.platform_vid.map(|v| v.to_string()).unwrap_or_default().into(),
464                )),
465            }))
466            .await?;
467
468        realm
469            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
470                name: "fuchsia.driver.testrealm.PlatformPid".parse()?,
471                value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
472                    args.platform_pid.map(|v| v.to_string()).unwrap_or_default().into(),
473                )),
474            }))
475            .await?;
476
477        let bind_eager = args.driver_bind_eager.unwrap_or_default();
478        realm
479            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
480                name: "fuchsia.driver.BindEager".parse()?,
481                value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::StringVector(
482                    bind_eager.into_iter().map(FlyStr::new).collect::<Box<[_]>>(),
483                )),
484            }))
485            .await?;
486
487        let driver_disable = args.driver_disable.unwrap_or_default();
488        realm
489            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
490                name: "fuchsia.driver.DisabledDrivers".parse()?,
491                value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::StringVector(
492                    driver_disable.into_iter().map(FlyStr::new).collect::<Box<[_]>>(),
493                )),
494            }))
495            .await?;
496
497        let driver_index_stop_timeout_millis = args.driver_index_stop_timeout_millis.unwrap_or(-1);
498        realm
499            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
500                name: "fuchsia.driver.index.StopOnIdleTimeoutMillis".parse()?,
501                value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Int64(
502                    driver_index_stop_timeout_millis,
503                )),
504            }))
505            .await?;
506        realm
507            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
508                name: "fuchsia.power.WaitForSuspendingToken".parse()?,
509                value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
510            }))
511            .await?;
512
513        let root_driver = match args.root_driver {
514            Some(val) => val,
515            None => "fuchsia-boot:///dtr#meta/test-parent-sys.cm".to_string(),
516        };
517        realm
518            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
519                name: "fuchsia.driver.manager.RootDriver".parse()?,
520                value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
521                    root_driver.into(),
522                )),
523            }))
524            .await?;
525
526        // Setup software device config capabilities.
527        let software_devs_src = match args.software_devices {
528            Some(devs) => {
529                let names = devs.iter().map(|dev| dev.device_name.clone()).collect::<Vec<_>>();
530                let ids = devs.iter().map(|dev| dev.device_id).collect::<Vec<_>>();
531                realm
532                    .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
533                        name: "fuchsia.platform.bus.SoftwareDeviceNames".parse()?,
534                        value: cm_rust::ConfigValue::Vector(
535                            cm_rust::ConfigVectorValue::StringVector(
536                                names.into_iter().map(FlyStr::new).collect::<Box<[_]>>(),
537                            ),
538                        ),
539                    }))
540                    .await?;
541                realm
542                    .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
543                        name: "fuchsia.platform.bus.SoftwareDeviceIds".parse()?,
544                        value: cm_rust::ConfigValue::Vector(
545                            cm_rust::ConfigVectorValue::Uint32Vector(ids.into()),
546                        ),
547                    }))
548                    .await?;
549                Ref::self_()
550            }
551            None => Ref::void(),
552        };
553
554        // Config routes.
555        realm
556            .add_route(
557                Route::new()
558                    .capability(Capability::configuration("fuchsia.driver.BindEager"))
559                    .capability(Capability::configuration("fuchsia.driver.DisabledDrivers"))
560                    .capability(Capability::configuration(
561                        "fuchsia.driver.index.StopOnIdleTimeoutMillis",
562                    ))
563                    .from(Ref::self_())
564                    .to(&driver_index),
565            )
566            .await?;
567
568        realm
569            .add_route(
570                Route::new()
571                    .capability(Capability::configuration("fuchsia.driver.manager.RootDriver"))
572                    .from(Ref::self_())
573                    .to(&driver_manager),
574            )
575            .await?;
576
577        realm
578            .add_route(
579                Route::new()
580                    .capability(
581                        Capability::configuration(
582                            "fuchsia.driver.manager.SetRootDriverHostCritical",
583                        )
584                        .optional(),
585                    )
586                    .capability(
587                        Capability::configuration("fuchsia.driver.manager.SuspendTimeoutFallback")
588                            .optional(),
589                    )
590                    .from(Ref::void())
591                    .to(&driver_manager),
592            )
593            .await?;
594
595        realm
596            .add_route(
597                Route::new()
598                    .capability(Capability::configuration(
599                        "fuchsia.driver.testrealm.TunnelBootItems",
600                    ))
601                    .capability(Capability::configuration("fuchsia.driver.testrealm.BoardName"))
602                    .capability(Capability::configuration("fuchsia.driver.testrealm.PlatformVid"))
603                    .capability(Capability::configuration("fuchsia.driver.testrealm.PlatformPid"))
604                    .from(Ref::self_())
605                    .to(&dtr_support),
606            )
607            .await?;
608
609        realm
610            .add_route(
611                Route::new()
612                    .capability(Capability::configuration("fuchsia.power.WaitForSuspendingToken"))
613                    .from(Ref::self_())
614                    .to(&driver_manager),
615            )
616            .await?;
617
618        realm
619            .add_route(
620                Route::new()
621                    .capability(
622                        Capability::configuration("fuchsia.platform.bus.SoftwareDeviceNames")
623                            .optional(),
624                    )
625                    .capability(
626                        Capability::configuration("fuchsia.platform.bus.SoftwareDeviceIds")
627                            .optional(),
628                    )
629                    .from(software_devs_src)
630                    .to(&boot_drivers),
631            )
632            .await?;
633
634        // Dynamic routes to the driver framework children.
635        realm
636            .add_route(
637                Route::new()
638                    .capability(Capability::protocol_by_name("fuchsia.driver.test.Internal"))
639                    .from(&driver_test_internal)
640                    .to(&fake_resolver),
641            )
642            .await?;
643
644        // Routes from the driver framework children out to the test.
645        self.add_route(
646            Route::new()
647                .capability(Capability::directory("dev-class"))
648                .capability(Capability::directory("dev-topological"))
649                .capability(Capability::protocol_by_name(
650                    "fuchsia.driver.registrar.DriverRegistrar",
651                ))
652                .capability(Capability::protocol_by_name("fuchsia.driver.development.Manager"))
653                .capability(Capability::protocol_by_name(
654                    "fuchsia.driver.framework.CompositeNodeManager",
655                ))
656                .capability(Capability::protocol_by_name("fuchsia.system.state.Administrator"))
657                .from(&realm)
658                .to(Ref::parent()),
659        )
660        .await?;
661        // LINT.ThenChange(/sdk/lib/driver_test_realm/realm_builder/cpp/builder.cc)
662        Ok(&self)
663    }
664}
665
666#[async_trait::async_trait]
667pub trait DriverTestRealmInstance {
668    /// Connect to the /dev/ directory hosted by  DriverTestRealm in this Instance.
669    fn driver_test_realm_connect_to_dev(&self) -> Result<fio::DirectoryProxy>;
670
671    /// Waits for the driver manager boot up logic to complete. This will ensure all
672    /// in-progress binds complete and indicates its safe to proceed with the test
673    /// or tear down the test realm with no errors.
674    async fn wait_for_bootup(&self) -> Result<()>;
675
676    /// Waits for the node matching the given moniker.
677    async fn wait_for_node(&self, moniker: &str) -> Result<fdd::NodeInfo>;
678}
679
680#[async_trait::async_trait]
681impl DriverTestRealmInstance for RealmInstance {
682    fn driver_test_realm_connect_to_dev(&self) -> Result<fio::DirectoryProxy> {
683        fuchsia_fs::directory::open_directory_async(
684            self.root.get_exposed_dir(),
685            "dev-topological",
686            fio::Flags::empty(),
687        )
688        .map_err(Into::into)
689    }
690
691    async fn wait_for_bootup(&self) -> Result<()> {
692        let manager: fdd::ManagerProxy = self.root.connect_to_protocol_at_exposed_dir()?;
693        manager.wait_for_bootup().await?;
694        Ok(())
695    }
696
697    async fn wait_for_node(&self, moniker: &str) -> Result<fdd::NodeInfo> {
698        let manager: fdd::ManagerProxy = self.root.connect_to_protocol_at_exposed_dir()?;
699        loop {
700            let (iterator, iterator_server) =
701                fidl::endpoints::create_proxy::<fdd::NodeInfoIteratorMarker>();
702            manager.get_node_info(&[moniker.to_string()], iterator_server, true)?;
703            let next = iterator.get_next().await;
704            if let Ok(nodes) = next
705                && !nodes.is_empty()
706                && nodes[0].moniker == Some(moniker.to_string())
707            {
708                return Ok(nodes[0].clone());
709            }
710        }
711    }
712}