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