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