| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // These tests use pseudo_directory, which can exceed the recursion limit. |
| // We could have refactored the tests to not hit the limit. Instead, we choose to optimize |
| // for test readability and increase the recursion limit. |
| #![recursion_limit = "512"] |
| |
| use { |
| fidl::endpoints::ServerEnd, |
| fidl_fuchsia_io::{ |
| self as fio, DirectoryMarker, DirectoryObject, DirectoryProxy, NodeInfo, NodeMarker, |
| }, |
| fidl_fuchsia_io2 as fio2, |
| fidl_fuchsia_pkg::{LocalMirrorMarker, LocalMirrorProxy}, |
| fuchsia_component::server::ServiceFs, |
| fuchsia_component_test::{builder::*, mock::MockHandles, RealmInstance}, |
| fuchsia_url::pkg_url::RepoUrl, |
| fuchsia_zircon::Status, |
| futures::{channel::oneshot, prelude::*}, |
| parking_lot::Mutex, |
| std::sync::Arc, |
| vfs::{ |
| directory::entry::{DirectoryEntry, EntryInfo}, |
| execution_scope::ExecutionScope, |
| path::Path, |
| pseudo_directory, |
| }, |
| }; |
| |
| mod get_blob; |
| mod get_metadata; |
| |
| const PKG_LOCAL_MIRROR_URL: &str = |
| "fuchsia-pkg://fuchsia.com/pkg-local-mirror-integration-tests#meta/pkg-local-mirror.cm"; |
| |
| struct TestEnvBuilder { |
| usb_dir: Option<Arc<dyn DirectoryEntry>>, |
| } |
| |
| impl TestEnvBuilder { |
| fn new() -> Self { |
| Self { usb_dir: None } |
| } |
| |
| fn usb_dir(mut self, usb_dir: Arc<dyn DirectoryEntry>) -> Self { |
| if self.usb_dir.is_some() { |
| panic!("USB dir is already set"); |
| } |
| self.usb_dir = Some(usb_dir); |
| self |
| } |
| |
| async fn build(self) -> TestEnv { |
| let usb_dir = self.usb_dir.unwrap_or_else(|| { |
| // Default pseudo-directory structure. |
| pseudo_directory! { |
| "0" => pseudo_directory! { |
| "fuchsia_pkg" => pseudo_directory! { |
| "blobs" => pseudo_directory! {}, |
| "repository_metadata" => pseudo_directory! {}, |
| }, |
| }, |
| } |
| }); |
| |
| const COMPONENT_UNDER_TEST: &str = "pkg-local-mirror"; |
| const USB_MOCK_COMPONENT: &str = "usb-source"; |
| |
| let mut builder = RealmBuilder::new().await.expect("created"); |
| |
| // Create the component-under-test (pkg-local-mirror) child component. |
| // This is the production component + manifest. |
| builder |
| .add_eager_component(COMPONENT_UNDER_TEST, ComponentSource::url(PKG_LOCAL_MIRROR_URL)) |
| .await |
| .expect("component added"); |
| |
| // Create a mock component that provides the mock `/usb` directory. |
| // The `/usb` directory that is served is configured in this builder. |
| builder |
| .add_component( |
| USB_MOCK_COMPONENT, |
| ComponentSource::mock(move |h: MockHandles| { |
| let proxy = spawn_vfs(usb_dir.clone()); |
| async move { |
| let mut fs = ServiceFs::new(); |
| fs.add_remote("usb", proxy); |
| fs.serve_connection(h.outgoing_dir.into_channel()) |
| .expect("serve mock ServiceFs"); |
| fs.collect::<()>().await; |
| Ok::<(), anyhow::Error>(()) |
| } |
| .boxed() |
| }), |
| ) |
| .await |
| .expect("mock component added"); |
| |
| // Route the mock `/usb` directory from the mock source to the component-under-test. |
| builder |
| .add_route(CapabilityRoute { |
| capability: Capability::directory( |
| "usb", |
| "/usb", |
| fio2::Operations::from_bits(fio2::R_STAR_DIR).unwrap(), |
| ), |
| source: RouteEndpoint::component(USB_MOCK_COMPONENT), |
| targets: vec![RouteEndpoint::component(COMPONENT_UNDER_TEST)], |
| }) |
| .expect("usb capability routed"); |
| |
| // Route the component-under-test's public FIDL protocol so that it is |
| // accessible by this test. |
| builder |
| .add_route(CapabilityRoute { |
| capability: Capability::protocol("fuchsia.pkg.LocalMirror"), |
| source: RouteEndpoint::component(COMPONENT_UNDER_TEST), |
| targets: vec![RouteEndpoint::AboveRoot], |
| }) |
| .expect("fuchsia.pkg.LocalMirror routed"); |
| |
| // Route the logging protocol so that the component-under-test can be |
| // debugged. |
| builder |
| .add_route(CapabilityRoute { |
| capability: Capability::protocol("fuchsia.logger.LogSink"), |
| source: RouteEndpoint::AboveRoot, |
| targets: vec![RouteEndpoint::component(COMPONENT_UNDER_TEST)], |
| }) |
| .expect("fuchsia.logger.LogSink routed"); |
| |
| TestEnv { instance: builder.build().create().await.expect("created") } |
| } |
| } |
| |
| struct TestEnv { |
| instance: RealmInstance, |
| } |
| |
| impl TestEnv { |
| fn builder() -> TestEnvBuilder { |
| TestEnvBuilder::new() |
| } |
| |
| /// Opens a connection to the LocalMirror FIDL service. |
| fn local_mirror_proxy(&self) -> LocalMirrorProxy { |
| self.instance.root.connect_to_protocol_at_exposed_dir::<LocalMirrorMarker>().unwrap() |
| } |
| } |
| |
| fn spawn_vfs(dir: Arc<dyn DirectoryEntry>) -> DirectoryProxy { |
| let (client_end, server_end) = fidl::endpoints::create_endpoints::<DirectoryMarker>().unwrap(); |
| let scope = vfs::execution_scope::ExecutionScope::new(); |
| dir.open( |
| scope, |
| fidl_fuchsia_io::OPEN_RIGHT_READABLE, |
| 0, |
| vfs::path::Path::empty(), |
| ServerEnd::new(server_end.into_channel()), |
| ); |
| client_end.into_proxy().unwrap() |
| } |
| |
| fn repo_url() -> RepoUrl { |
| "fuchsia-pkg://fuchsia.com".parse().unwrap() |
| } |
| |
| /// The purpose of this DirectoryEntry impl (for use with vfs) is to guarantee that channels to usb |
| /// subdirectories /usb/0/fuchsia_pkg/[repository_metadata|blobs] are closed before |
| /// pkg-local-mirror makes open calls on them, so that the open calls fail with fidl errors. |
| /// In practice, this will prevent flakes in the tests. |
| struct DropAndSignal(Mutex<Option<oneshot::Sender<()>>>); |
| |
| impl DropAndSignal { |
| /// Creates a new `Arc<dyn DirectoryEntry>` which when opened, reports a successful open event |
| /// on the pipelined channel before dropping it. `closed_sender` is signaled, guaranteeing to |
| /// the caller that the directory is no longer open once the signal is received. |
| fn new(closed_sender: oneshot::Sender<()>) -> Arc<DropAndSignal> { |
| Arc::new(DropAndSignal(Mutex::new(Some(closed_sender)))) |
| } |
| } |
| |
| impl DirectoryEntry for DropAndSignal { |
| fn open( |
| self: Arc<Self>, |
| _scope: ExecutionScope, |
| flags: u32, |
| _mode: u32, |
| _path: Path, |
| server_end: ServerEnd<NodeMarker>, |
| ) { |
| assert!(flags & fio::OPEN_FLAG_DESCRIBE != 0); |
| let (_, ch) = server_end.into_stream_and_control_handle().unwrap(); |
| |
| // Need to send OnOpen because of the Describe flag. |
| ch.send_on_open_(Status::OK.into_raw(), Some(&mut NodeInfo::Directory(DirectoryObject))) |
| .unwrap(); |
| |
| // Make sure the connection is dropped before signalling. |
| drop(ch); |
| |
| match self.0.lock().take() { |
| Some(sender) => { |
| // Signal that the connection to this directory is dropped. |
| sender.send(()).unwrap(); |
| } |
| None => { |
| panic!("already signaled"); |
| } |
| } |
| } |
| |
| fn can_hardlink(&self) -> bool { |
| false |
| } |
| |
| fn entry_info(&self) -> EntryInfo { |
| EntryInfo::new(fio::INO_UNKNOWN, fio::DIRENT_TYPE_DIRECTORY) |
| } |
| } |