Skip to main content

fshost_test_fixture/
lib.rs

1// Copyright 2022 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 assert_matches::assert_matches;
6use diagnostics_assertions::assert_data_tree;
7use diagnostics_reader::ArchiveReader;
8use disk_builder::Disk;
9use fake_keymint::FakeKeymint;
10use fidl::endpoints::{ServiceMarker as _, create_proxy};
11use fidl_fuchsia_boot as fboot;
12use fidl_fuchsia_driver_test as fdt;
13use fidl_fuchsia_driver_token as ftoken;
14use fidl_fuchsia_feedback as ffeedback;
15use fidl_fuchsia_fshost_fxfsprovisioner as ffxfsprovisioner;
16use fidl_fuchsia_fxfs::{BlobReaderMarker, CryptManagementProxy, CryptProxy, KeyPurpose};
17use fidl_fuchsia_hardware_block_volume as fvolume;
18use fidl_fuchsia_hardware_inlineencryption as finline;
19use fidl_fuchsia_hardware_ramdisk as framdisk;
20use fidl_fuchsia_io as fio;
21use fidl_fuchsia_security_keymint as fkeymint;
22use fidl_fuchsia_storage_block as fblock;
23use fidl_fuchsia_storage_partitions as fpartitions;
24use fuchsia_async::{self as fasync, TimeoutExt as _};
25use fuchsia_component::client::{
26    connect_to_named_protocol_at_dir_root, connect_to_protocol_at_dir_root,
27};
28use fuchsia_component_test::{Capability, ChildOptions, RealmBuilder, RealmInstance, Ref, Route};
29use fuchsia_driver_test::{DriverTestRealmBuilder, DriverTestRealmInstance};
30use futures::channel::mpsc;
31use futures::{FutureExt as _, StreamExt as _};
32use ramdevice_client::{RamdiskClient, RamdiskClientBuilder};
33use std::pin::pin;
34use std::sync::Arc;
35use std::time::Duration;
36use test_vmo_backed_block_server::VmoBackedServer;
37
38pub mod disk_builder;
39mod mocks;
40
41pub use disk_builder::write_blob;
42pub use fshost_assembly_config::{BlockDeviceConfig, BlockDeviceIdentifiers, BlockDeviceParent};
43
44pub const VFS_TYPE_BLOBFS: u32 = 0x9e694d21;
45pub const VFS_TYPE_MINFS: u32 = 0x6e694d21;
46pub const VFS_TYPE_MEMFS: u32 = 0x3e694d21;
47pub const VFS_TYPE_FXFS: u32 = 0x73667866;
48pub const VFS_TYPE_F2FS: u32 = 0xfe694d21;
49pub const STARNIX_VOLUME_NAME: &str = "starnix_volume";
50
51const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60);
52
53async fn with_timeout<F: std::future::Future>(fut: F, name: impl Into<String>) -> F::Output {
54    let name = name.into();
55    fut.on_timeout(DEFAULT_TIMEOUT, move || panic!("{name} timed out after {DEFAULT_TIMEOUT:?}"))
56        .await
57}
58
59pub fn round_down<
60    T: Into<U>,
61    U: Copy + std::ops::Rem<U, Output = U> + std::ops::Sub<U, Output = U>,
62>(
63    offset: U,
64    block_size: T,
65) -> U {
66    let block_size = block_size.into();
67    offset - offset % block_size
68}
69
70pub struct TestFixtureBuilder {
71    no_fuchsia_boot: bool,
72    disk: Option<Disk>,
73    extra_disks: Vec<Disk>,
74    fshost: fshost_testing::FshostBuilder,
75    zbi_ramdisk: Option<disk_builder::DiskBuilder>,
76    force_fxfs_provisioner_failure: bool,
77    keymint: std::sync::Arc<FakeKeymint>,
78    crypt_policy: crypt_policy::Policy,
79    simulated_gpt: Option<Arc<VmoBackedServer>>,
80}
81
82impl TestFixtureBuilder {
83    pub fn new(fshost_component_name: &'static str) -> Self {
84        Self {
85            no_fuchsia_boot: false,
86            disk: None,
87            extra_disks: Vec::new(),
88            fshost: fshost_testing::FshostBuilder::new(fshost_component_name),
89            zbi_ramdisk: None,
90            force_fxfs_provisioner_failure: false,
91            keymint: std::sync::Arc::new(FakeKeymint::default()),
92            crypt_policy: crypt_policy::Policy::Null,
93            simulated_gpt: None,
94        }
95    }
96
97    pub fn fshost(&mut self) -> &mut fshost_testing::FshostBuilder {
98        &mut self.fshost
99    }
100
101    pub fn keymint(&mut self) -> std::sync::Arc<FakeKeymint> {
102        self.keymint.clone()
103    }
104
105    pub fn with_keymint_instance(mut self, keymint: std::sync::Arc<FakeKeymint>) -> Self {
106        self.keymint = keymint.clone();
107        if let Some(Disk::Builder(ref mut disk_builder)) = self.disk {
108            disk_builder.with_keymint_instance(keymint.clone());
109        }
110        for disk in &mut self.extra_disks {
111            if let Disk::Builder(disk_builder) = disk {
112                disk_builder.with_keymint_instance(keymint.clone());
113            }
114        }
115        self
116    }
117
118    pub fn with_disk(&mut self) -> &mut disk_builder::DiskBuilder {
119        self.disk = Some(Disk::Builder(disk_builder::DiskBuilder::new()));
120        self.disk
121            .as_mut()
122            .unwrap()
123            .builder()
124            .with_crypt_policy(self.crypt_policy)
125            .with_keymint_instance(self.keymint.clone());
126        self.disk.as_mut().unwrap().builder()
127    }
128
129    pub fn with_extra_disk(&mut self) -> &mut disk_builder::DiskBuilder {
130        self.extra_disks.push(Disk::Builder(disk_builder::DiskBuilder::new()));
131        self.extra_disks
132            .last_mut()
133            .unwrap()
134            .builder()
135            .with_crypt_policy(self.crypt_policy)
136            .with_keymint_instance(self.keymint.clone());
137        self.extra_disks.last_mut().unwrap().builder()
138    }
139
140    pub fn with_uninitialized_disk(mut self) -> Self {
141        self.disk = Some(Disk::Builder(disk_builder::DiskBuilder::uninitialized()));
142        self
143    }
144
145    pub fn with_disk_from(mut self, disk: Disk) -> Self {
146        self.disk = Some(disk);
147        self
148    }
149
150    pub fn with_simulated_gpt(mut self, server: Arc<VmoBackedServer>) -> Self {
151        self.simulated_gpt = Some(server);
152        self
153    }
154
155    pub fn with_zbi_ramdisk(&mut self) -> &mut disk_builder::DiskBuilder {
156        self.zbi_ramdisk = Some(disk_builder::DiskBuilder::new());
157        self.zbi_ramdisk.as_mut().unwrap()
158    }
159
160    pub fn no_fuchsia_boot(mut self) -> Self {
161        self.no_fuchsia_boot = true;
162        self
163    }
164
165    pub fn with_device_config(mut self, device_config: Vec<BlockDeviceConfig>) -> Self {
166        self.fshost.set_device_config(device_config);
167        self
168    }
169
170    pub fn with_crypt_policy(mut self, policy: crypt_policy::Policy) -> Self {
171        self.fshost.set_crypt_policy(policy);
172        self.crypt_policy = policy;
173        if let Some(Disk::Builder(ref mut disk_builder)) = self.disk {
174            disk_builder.with_crypt_policy(policy);
175        }
176        for disk in &mut self.extra_disks {
177            if let Disk::Builder(disk_builder) = disk {
178                disk_builder.with_crypt_policy(policy);
179            }
180        }
181        self
182    }
183
184    pub fn force_fxfs_provisioner_failure(mut self) -> Self {
185        self.force_fxfs_provisioner_failure = true;
186        self
187    }
188
189    pub async fn build(self) -> TestFixture {
190        let builder = RealmBuilder::new().await.unwrap();
191        let fshost = self.fshost.build(&builder).await;
192        // Route fshost's inline encryption Device capability to the parent.
193        builder
194            .add_route(
195                Route::new()
196                    .capability(Capability::protocol::<finline::DeviceMarker>())
197                    .from(&fshost)
198                    .to(Ref::parent()),
199            )
200            .await
201            .unwrap();
202
203        builder
204            .add_route(
205                Route::new()
206                    .capability(Capability::protocol::<fidl_fuchsia_component::RealmMarker>())
207                    .from(Ref::framework())
208                    .to(Ref::parent()),
209            )
210            .await
211            .unwrap();
212
213        let maybe_zbi_vmo = match self.zbi_ramdisk {
214            Some(disk_builder) => Some(disk_builder.build_as_zbi_ramdisk().await),
215            None => None,
216        };
217        let (tx, crash_reports) = mpsc::channel(32);
218        let mocks = mocks::new_mocks(
219            maybe_zbi_vmo,
220            tx,
221            self.force_fxfs_provisioner_failure,
222            self.keymint.clone(),
223            self.simulated_gpt.clone(),
224        );
225
226        let mocks = builder
227            .add_local_child("mocks", move |h| mocks(h).boxed(), ChildOptions::new())
228            .await
229            .unwrap();
230        builder
231            .add_route(
232                Route::new()
233                    .capability(Capability::protocol::<fkeymint::SealingKeysMarker>())
234                    .capability(Capability::protocol::<fkeymint::AdminMarker>())
235                    .from(&mocks)
236                    .to(Ref::parent()),
237            )
238            .await
239            .unwrap();
240        builder
241            .add_route(
242                Route::new()
243                    .capability(Capability::protocol::<ffeedback::CrashReporterMarker>())
244                    .capability(Capability::protocol::<ffxfsprovisioner::FxfsProvisionerMarker>())
245                    .capability(Capability::protocol::<fkeymint::SealingKeysMarker>())
246                    .capability(Capability::protocol::<fkeymint::AdminMarker>())
247                    .capability(Capability::protocol::<ftoken::NodeBusTopologyMarker>())
248                    .from(&mocks)
249                    .to(&fshost),
250            )
251            .await
252            .unwrap();
253        builder
254            .add_route(
255                Route::new()
256                    .capability(Capability::service::<fvolume::ServiceMarker>())
257                    .from(&mocks)
258                    .to(&fshost),
259            )
260            .await
261            .unwrap();
262        if !self.no_fuchsia_boot {
263            builder
264                .add_route(
265                    Route::new()
266                        .capability(Capability::protocol::<fboot::ArgumentsMarker>())
267                        .capability(Capability::protocol::<fboot::ItemsMarker>())
268                        .from(&mocks)
269                        .to(&fshost),
270                )
271                .await
272                .unwrap();
273        }
274
275        builder
276            .add_route(
277                Route::new()
278                    .capability(Capability::dictionary("diagnostics"))
279                    .from(Ref::parent())
280                    .to(&fshost),
281            )
282            .await
283            .unwrap();
284
285        let dtr_exposes = vec![
286            fidl_fuchsia_component_test::Capability::Service(
287                fidl_fuchsia_component_test::Service {
288                    name: Some("fuchsia.hardware.ramdisk.Service".to_owned()),
289                    ..Default::default()
290                },
291            ),
292            fidl_fuchsia_component_test::Capability::Service(
293                fidl_fuchsia_component_test::Service {
294                    name: Some("fuchsia.hardware.block.volume.Service".to_owned()),
295                    ..Default::default()
296                },
297            ),
298        ];
299        builder.driver_test_realm_setup().await.unwrap();
300        builder.driver_test_realm_add_dtr_exposes(&dtr_exposes).await.unwrap();
301        builder
302            .add_route(
303                Route::new()
304                    .capability(Capability::directory("dev-topological").rights(fio::R_STAR_DIR))
305                    .capability(Capability::service::<fvolume::ServiceMarker>())
306                    .from(Ref::child(fuchsia_driver_test::COMPONENT_NAME))
307                    .to(&fshost),
308            )
309            .await
310            .unwrap();
311        builder
312            .add_route(
313                Route::new()
314                    .capability(
315                        Capability::directory("dev-class")
316                            .rights(fio::R_STAR_DIR)
317                            .subdir("block")
318                            .as_("dev-class-block"),
319                    )
320                    .from(Ref::child(fuchsia_driver_test::COMPONENT_NAME))
321                    .to(Ref::parent()),
322            )
323            .await
324            .unwrap();
325
326        let realm = builder.build().await.unwrap();
327        let realm_proxy = connect_to_protocol_at_dir_root::<fidl_fuchsia_component::RealmMarker>(
328            realm.root.get_exposed_dir(),
329        )
330        .expect("failed to connect to Realm");
331        let (controller, controller_server_end) =
332            create_proxy::<fidl_fuchsia_component::ControllerMarker>();
333        realm_proxy
334            .open_controller(
335                &fidl_fuchsia_component_decl::ChildRef {
336                    name: "test-fshost".to_string(),
337                    collection: None,
338                },
339                controller_server_end,
340            )
341            .await
342            .expect("failed to open controller")
343            .expect("open controller error");
344        let (execution_controller, execution_controller_server_end) =
345            create_proxy::<fidl_fuchsia_component::ExecutionControllerMarker>();
346        controller
347            .start(
348                fidl_fuchsia_component::StartChildArgs::default(),
349                execution_controller_server_end,
350            )
351            .await
352            .expect("failed to start fshost")
353            .expect("start fshost error");
354
355        let mut fixture = TestFixture {
356            realm,
357            ramdisks: Vec::new(),
358            main_disk: None,
359            crash_reports,
360            execution_controller,
361            torn_down: TornDown(false),
362        };
363
364        log::info!(
365            realm_name:? = fixture.realm.root.child_name();
366            "built new test realm",
367        );
368
369        fixture
370            .realm
371            .driver_test_realm_start(fdt::RealmArgs {
372                root_driver: Some("fuchsia-boot:///platform-bus#meta/platform-bus.cm".to_owned()),
373                dtr_exposes: Some(dtr_exposes),
374                software_devices: Some(vec![
375                    fdt::SoftwareDevice {
376                        device_name: "ram-disk".to_string(),
377                        device_id: bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_RAM_DISK,
378                    },
379                    fdt::SoftwareDevice {
380                        device_name: "ram-nand".to_string(),
381                        device_id: bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_RAM_NAND,
382                    },
383                ]),
384                ..Default::default()
385            })
386            .await
387            .unwrap();
388
389        // The order of adding disks matters here, unfortunately. fshost should not change behavior
390        // based on the order disks appear, but because we take the first available that matches
391        // whatever relevant criteria, it's useful to test that matchers don't get clogged up by
392        // previous disks.
393        // TODO(https://fxbug.dev/380353856): This type of testing should be irrelevant once the
394        // block devices are determined by configuration options instead of heuristically.
395        for disk in self.extra_disks.into_iter() {
396            fixture.add_disk(disk).await;
397        }
398        if let Some(disk) = self.disk {
399            fixture.add_main_disk(disk).await;
400        }
401
402        fixture
403    }
404}
405
406/// Create a separate struct that does the drop-assert because fixture.tear_down can't call
407/// realm.destroy if it has the drop impl itself.
408struct TornDown(bool);
409
410impl Drop for TornDown {
411    fn drop(&mut self) {
412        // Because tear_down is async, it needs to be called by the test in an async context. It
413        // checks some properties so for correctness it must be called.
414        assert!(self.0, "fixture.tear_down() must be called");
415    }
416}
417
418pub struct TestFixture {
419    pub realm: RealmInstance,
420    pub ramdisks: Vec<RamdiskClient>,
421    pub main_disk: Option<Disk>,
422    pub crash_reports: mpsc::Receiver<ffeedback::CrashReport>,
423    pub execution_controller: fidl_fuchsia_component::ExecutionControllerProxy,
424    torn_down: TornDown,
425}
426
427impl TestFixture {
428    pub async fn tear_down(mut self) -> Option<Disk> {
429        log::info!(realm_name:? = self.realm.root.child_name(); "tearing down");
430        let disk = self.main_disk.take();
431        // Check the crash reports before destroying the realm because tearing down the realm can
432        // cause mounting errors that trigger a crash report.
433        assert_matches!(self.crash_reports.try_recv(), Err(_));
434        self.realm.destroy().await.unwrap();
435        self.torn_down.0 = true;
436        disk
437    }
438
439    pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
440        self.realm.root.get_exposed_dir()
441    }
442
443    pub fn dir(&self, dir: &str, flags: fio::Flags) -> fio::DirectoryProxy {
444        let (dev, server) = create_proxy::<fio::DirectoryMarker>();
445        let flags = flags | fio::Flags::PROTOCOL_DIRECTORY;
446        self.realm
447            .root
448            .get_exposed_dir()
449            .open(dir, flags, &fio::Options::default(), server.into_channel())
450            .expect("open failed");
451        dev
452    }
453
454    pub async fn check_fs_type(&self, dir: &str, fs_type: u32) {
455        let (status, info) = with_timeout(
456            self.dir(dir, fio::PERM_READABLE).query_filesystem(),
457            format!("check_fs_type({dir})"),
458        )
459        .await
460        .expect("query failed");
461        assert_eq!(status, zx::sys::ZX_OK);
462        assert!(info.is_some());
463        let info_type = info.unwrap().fs_type;
464        assert_eq!(info_type, fs_type, "{:#08x} != {:#08x}", info_type, fs_type);
465    }
466
467    pub async fn check_test_blob(&self) {
468        with_timeout(
469            async {
470                let expected_blob_hash = disk_builder::test_blob_hash();
471                let reader = connect_to_protocol_at_dir_root::<BlobReaderMarker>(
472                    self.realm.root.get_exposed_dir(),
473                )
474                .expect("failed to connect to the BlobReader");
475                let _vmo = reader
476                    .get_vmo(&expected_blob_hash.into())
477                    .await
478                    .expect("blob get_vmo fidl error")
479                    .unwrap_or_else(|e| match zx::Status::err_from_raw(e) {
480                        zx::Status::NOT_FOUND => panic!("Test blob not found - blobfs lost data!"),
481                        s => panic!("Error while opening test blob vmo: {s}"),
482                    });
483            },
484            "check_test_blob",
485        )
486        .await
487    }
488
489    /// Check for the existence of a well-known set of test files in the data volume. These files
490    /// are placed by the disk builder if it formats the filesystem beforehand.
491    pub async fn check_test_data_file(&self) {
492        with_timeout(
493            async {
494                let (file, server) = create_proxy::<fio::NodeMarker>();
495                self.dir("data", fio::PERM_READABLE)
496                    .open(
497                        ".testdata",
498                        fio::PERM_READABLE,
499                        &fio::Options::default(),
500                        server.into_channel(),
501                    )
502                    .expect("open failed");
503                file.get_attributes(fio::NodeAttributesQuery::empty())
504                    .await
505                    .expect("Fidl transport error on get_attributes()")
506                    .expect("get_attr failed - data was probably deleted!");
507
508                let data = self.dir("data", fio::PERM_READABLE);
509                fuchsia_fs::directory::open_file(&data, ".testdata", fio::PERM_READABLE)
510                    .await
511                    .unwrap();
512
513                fuchsia_fs::directory::open_directory(&data, "ssh", fio::PERM_READABLE)
514                    .await
515                    .unwrap();
516                fuchsia_fs::directory::open_directory(&data, "ssh/config", fio::PERM_READABLE)
517                    .await
518                    .unwrap();
519                fuchsia_fs::directory::open_directory(&data, "problems", fio::PERM_READABLE)
520                    .await
521                    .unwrap();
522
523                let authorized_keys = fuchsia_fs::directory::open_file(
524                    &data,
525                    "ssh/authorized_keys",
526                    fio::PERM_READABLE,
527                )
528                .await
529                .unwrap();
530                assert_eq!(
531                    &fuchsia_fs::file::read_to_string(&authorized_keys).await.unwrap(),
532                    "public key!"
533                );
534            },
535            "check_test_data_file",
536        )
537        .await
538    }
539
540    /// Checks for the absence of the .testdata marker file, indicating the data filesystem was
541    /// reformatted.
542    pub async fn check_test_data_file_absent(&self) {
543        let err = with_timeout(
544            fuchsia_fs::directory::open_file(
545                &self.dir("data", fio::PERM_READABLE),
546                ".testdata",
547                fio::PERM_READABLE,
548            ),
549            "check_test_data_file_absent",
550        )
551        .await
552        .expect_err("open_file failed");
553        assert!(err.is_not_found_error());
554    }
555
556    pub async fn add_main_disk(&mut self, disk: Disk) {
557        assert!(self.main_disk.is_none());
558        let (vmo, type_guid) = disk.into_vmo_and_type_guid().await;
559        let vmo_clone =
560            vmo.create_child(zx::VmoChildOptions::SLICE, 0, vmo.get_size().unwrap()).unwrap();
561
562        self.add_ramdisk(vmo, type_guid).await;
563        self.main_disk = Some(Disk::Prebuilt(vmo_clone, type_guid));
564    }
565
566    pub async fn add_disk(&mut self, disk: Disk) {
567        let (vmo, type_guid) = disk.into_vmo_and_type_guid().await;
568        self.add_ramdisk(vmo, type_guid).await;
569    }
570
571    async fn add_ramdisk(&mut self, vmo: zx::Vmo, type_guid: Option<[u8; 16]>) {
572        let mut ramdisk_builder = RamdiskClientBuilder::new_with_vmo(vmo, Some(512))
573            .publish()
574            .ramdisk_service(self.dir(framdisk::ServiceMarker::SERVICE_NAME, fio::PERM_READABLE));
575        if let Some(guid) = type_guid {
576            ramdisk_builder = ramdisk_builder.guid(guid);
577        }
578        let mut ramdisk = pin!(ramdisk_builder.build().fuse());
579
580        let ramdisk = futures::select_biased!(
581            res = ramdisk => res,
582            _ = fasync::Timer::new(Duration::from_secs(120))
583                .fuse() => panic!("Timed out waiting for RamdiskClient"),
584        )
585        .unwrap();
586        self.ramdisks.push(ramdisk);
587    }
588
589    pub fn connect_to_crypt(&self) -> CryptProxy {
590        self.realm
591            .root
592            .connect_to_protocol_at_exposed_dir()
593            .expect("connect_to_protocol_at_exposed_dir failed for the Crypt protocol")
594    }
595
596    pub async fn setup_starnix_crypt(&self) -> (CryptProxy, CryptManagementProxy) {
597        let crypt_management: CryptManagementProxy =
598            self.realm.root.connect_to_protocol_at_exposed_dir().expect(
599                "connect_to_protocol_at_exposed_dir failed for the CryptManagement protocol",
600            );
601        let crypt = self
602            .realm
603            .root
604            .connect_to_protocol_at_exposed_dir()
605            .expect("connect_to_protocol_at_exposed_dir failed for the Crypt protocol");
606        let key = vec![0xABu8; 32];
607        crypt_management
608            .add_wrapping_key(&u128::to_le_bytes(0), key.as_slice())
609            .await
610            .expect("fidl transport error")
611            .expect("add wrapping key failed");
612        crypt_management
613            .add_wrapping_key(&u128::to_le_bytes(1), key.as_slice())
614            .await
615            .expect("fidl transport error")
616            .expect("add wrapping key failed");
617        crypt_management
618            .set_active_key(KeyPurpose::Data, &u128::to_le_bytes(0))
619            .await
620            .expect("fidl transport error")
621            .expect("set metadata key failed");
622        crypt_management
623            .set_active_key(KeyPurpose::Metadata, &u128::to_le_bytes(1))
624            .await
625            .expect("fidl transport error")
626            .expect("set metadata key failed");
627        (crypt, crypt_management)
628    }
629
630    /// This must be called if any crash reports are expected, since spurious reports will cause a
631    /// failure in TestFixture::tear_down.
632    pub async fn wait_for_crash_reports(
633        &mut self,
634        count: usize,
635        expected_program: &'_ str,
636        expected_signature: &'_ str,
637    ) {
638        log::info!("Waiting for {count} crash reports");
639        for _ in 0..count {
640            let report = self.crash_reports.next().await.expect("Sender closed");
641            assert_eq!(report.program_name.as_deref(), Some(expected_program));
642            assert_eq!(report.crash_signature.as_deref(), Some(expected_signature));
643        }
644        if count > 0 {
645            let selector =
646                format!("realm_builder\\:{}/test-fshost:root", self.realm.root.child_name());
647            log::info!("Checking inspect for corruption event, selector={selector}");
648            let tree = ArchiveReader::inspect()
649                .add_selector(selector)
650                .snapshot()
651                .await
652                .unwrap()
653                .into_iter()
654                .next()
655                .and_then(|result| result.payload)
656                .expect("expected one inspect hierarchy");
657
658            let format = || expected_program.to_string();
659            if expected_signature.contains("unseal-error") {
660                assert_data_tree!(tree, root: contains {
661                    keymint_unseal_failure_events: contains {
662                        format() => 1u64,
663                    }
664                });
665            } else {
666                assert_data_tree!(tree, root: contains {
667                    corruption_events: contains {
668                        format() => 1u64,
669                    }
670                });
671            }
672        }
673    }
674
675    // Check that the system partition table contains partitions with labels found in `expected`.
676    pub async fn check_system_partitions(&self, mut expected: Vec<&str>) {
677        with_timeout(
678            async {
679                let partitions =
680                    self.dir(fpartitions::PartitionServiceMarker::SERVICE_NAME, fio::PERM_READABLE);
681                let entries = fuchsia_fs::directory::readdir(&partitions)
682                    .await
683                    .expect("Failed to read partitions");
684
685                assert_eq!(entries.len(), expected.len());
686
687                let mut found_partition_labels = Vec::new();
688                for entry in entries {
689                    let endpoint_name = format!("{}/volume", entry.name);
690                    let volume = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(
691                        &partitions,
692                        &endpoint_name,
693                    )
694                    .expect("failed to connect to named protocol at dir root");
695                    let (raw_status, label) =
696                        volume.get_name().await.expect("failed to call get_name");
697                    zx::Status::ok(raw_status).expect("get_name status failed");
698                    found_partition_labels
699                        .push(label.expect("partition label expected to be some value"));
700                }
701                found_partition_labels.sort();
702                expected.sort();
703                assert_eq!(found_partition_labels, expected);
704            },
705            "check_system_partitions",
706        )
707        .await
708    }
709}