blob: beba0783aa0220be2088937f1d0404e5c4a4f725 [file] [log] [blame]
// 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)
}
}