blob: 70aad40d352db9905b26e4462503ace86acfac69 [file] [log] [blame]
// Copyright 2019 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.
use {
crate::error::*,
anyhow::Error,
cm_rust,
fidl::endpoints::ServerEnd,
fidl_fuchsia_diagnostics as fdiagnostics, fidl_fuchsia_io2 as fio2, fidl_fuchsia_test as ftest,
fidl_fuchsia_test_manager as ftest_manager,
ftest::SuiteMarker,
ftest_manager::{LaunchError, SuiteControllerRequestStream},
fuchsia_async as fasync,
fuchsia_component::server::ServiceFs,
futures::prelude::*,
lazy_static::lazy_static,
std::sync::Arc,
topology_builder::{
builder::{
Capability, CapabilityRoute, ComponentSource, Event, RouteEndpoint, TopologyBuilder,
},
error::Error as TopologyBuilderError,
mock::{Mock, MockHandles},
Topology, TopologyInstance,
},
tracing::{error, warn},
};
mod diagnostics;
mod error;
pub const TEST_ROOT_REALM_NAME: &'static str = "test_root";
pub const WRAPPER_ROOT_REALM_PATH: &'static str = "test_wrapper/test_root";
lazy_static! {
static ref ARCHIVIST_FOR_EMBEDDING_URL: &'static str =
"fuchsia-pkg://fuchsia.com/test_manager#meta/archivist-for-embedding.cm";
static ref READ_RIGHTS: fio2::Operations = fio2::Operations::Connect
| fio2::Operations::Enumerate
| fio2::Operations::Traverse
| fio2::Operations::ReadBytes
| fio2::Operations::GetAttributes;
static ref READ_WRITE_RIGHTS: fio2::Operations = fio2::Operations::Connect
| fio2::Operations::Enumerate
| fio2::Operations::Traverse
| fio2::Operations::ReadBytes
| fio2::Operations::WriteBytes
| fio2::Operations::ModifyDirectory
| fio2::Operations::GetAttributes
| fio2::Operations::UpdateAttributes;
static ref ADMIN_RIGHTS: fio2::Operations = fio2::Operations::Admin;
}
/// Start test manager and serve it over `stream`.
pub async fn run_test_manager(
mut stream: ftest_manager::HarnessRequestStream,
) -> Result<(), TestManagerError> {
while let Some(event) = stream.try_next().await.map_err(TestManagerError::Stream)? {
match event {
ftest_manager::HarnessRequest::LaunchSuite {
test_url,
options: _,
suite,
controller,
responder,
} => {
let controller = match controller.into_stream() {
Err(error) => {
error!(%error, component_url = %test_url, "invalid controller channel");
responder
.send(&mut Err(LaunchError::InvalidArgs))
.map_err(TestManagerError::Response)?;
// process next request
continue;
}
Ok(c) => c,
};
match launch_test(&test_url, suite).await {
Ok(test) => {
responder.send(&mut Ok(())).map_err(TestManagerError::Response)?;
fasync::Task::spawn(async move {
test.serve_controller(controller).await.unwrap_or_else(|error| {
error!(%error, component_url = %test_url, "serve_controller failed");
});
})
.detach();
}
Err(e) => {
error!("Failed to launch test: {:?}", e);
responder.send(&mut Err(e.into())).map_err(TestManagerError::Response)?;
}
}
}
}
}
Ok(())
}
struct RunningTest {
instance: TopologyInstance,
// TODO: use this archive accessor to fetch isolated logs.
_archive_accessor: Arc<fdiagnostics::ArchiveAccessorProxy>,
}
impl RunningTest {
async fn destroy(mut self) {
let destroy_waiter = self.instance.root.take_destroy_waiter();
drop(self);
destroy_waiter.await;
}
/// Serves Suite controller and destroys this test afterwards.
pub async fn serve_controller(
self,
mut stream: SuiteControllerRequestStream,
) -> Result<(), Error> {
while let Some(event) = stream.try_next().await? {
match event {
ftest_manager::SuiteControllerRequest::Kill { .. } => {
self.destroy().await;
return Ok(());
}
}
}
self.destroy().await;
Ok(())
}
}
/// Launch test and return the name of test used to launch it in collection.
async fn launch_test(
test_url: &str,
suite_request: ServerEnd<SuiteMarker>,
) -> Result<RunningTest, LaunchTestError> {
// This archive accessor will be served by the embedded archivist.
let (archive_accessor, archive_accessor_server_end) =
fidl::endpoints::create_proxy::<fdiagnostics::ArchiveAccessorMarker>()
.map_err(LaunchTestError::CreateProxyForArchiveAccessor)?;
let archive_accessor_arc = Arc::new(archive_accessor);
let mut topology = get_topology(archive_accessor_arc.clone(), test_url)
.await
.map_err(LaunchTestError::InitializeTestTopology)?;
topology.set_collection_name("tests");
let topology_instance = topology.create().await.map_err(LaunchTestError::CreateTestTopology)?;
topology_instance
.root
.connect_request_to_protocol_at_exposed_dir::<fdiagnostics::ArchiveAccessorMarker>(
archive_accessor_server_end,
)
.map_err(LaunchTestError::ConnectToArchiveAccessor)?;
topology_instance
.root
.connect_request_to_protocol_at_exposed_dir(suite_request)
.map_err(LaunchTestError::ConnectToTestSuite)?;
Ok(RunningTest { instance: topology_instance, _archive_accessor: archive_accessor_arc })
}
async fn get_topology(
archive_accessor: Arc<fdiagnostics::ArchiveAccessorProxy>,
test_url: &str,
) -> Result<Topology, TopologyBuilderError> {
let mut builder = TopologyBuilder::new().await?;
let test_wrapper_test_root = format!("test_wrapper/{}", TEST_ROOT_REALM_NAME);
builder
.add_eager_component(test_wrapper_test_root.as_ref(), ComponentSource::url(test_url))
.await?
.add_component(
"mocks-server",
ComponentSource::Mock(Mock::new(move |mock_handles| {
Box::pin(serve_mocks(archive_accessor.clone(), mock_handles))
})),
)
.await?
.add_component("test_wrapper/archivist", ComponentSource::url(*ARCHIVIST_FOR_EMBEDDING_URL))
.await?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.process.Launcher"),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.boot.WriteOnlyLog"),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.sys2.EventSource"),
source: RouteEndpoint::AboveRoot,
targets: vec![
RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH),
RouteEndpoint::component("test_wrapper/archivist"),
],
})?
.add_route(CapabilityRoute {
capability: Capability::storage("temp", "/tmp"),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.logger.LogSink"),
source: RouteEndpoint::AboveRoot,
targets: vec![
RouteEndpoint::component("test_wrapper/archivist"),
// TODO(fxbug.dev/67960): use embedded archivist LogSink)
RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH),
],
})?
// TODO(fxbug.dev/67960): uncomment when fetching isolated logs is implemented.
// .add_route(CapabilityRoute {
// capability: Capability::protocol("fuchsia.logger.LogSink"),
// source: RouteEndpoint::component("test_wrapper/archivist"),
// targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
// })?
// .add_route(CapabilityRoute {
// capability: Capability::protocol("fuchsia.logger.Log"),
// source: RouteEndpoint::component("test_wrapper/archivist"),
// targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
// })?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.diagnostics.ArchiveAccessor"),
source: RouteEndpoint::component("mocks-server"),
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.diagnostics.ArchiveAccessor"),
source: RouteEndpoint::component("test_wrapper/archivist"),
targets: vec![RouteEndpoint::AboveRoot],
})?
.add_route(CapabilityRoute {
capability: Capability::Event(Event::Started, cm_rust::EventMode::Async),
source: RouteEndpoint::component("test_wrapper"),
targets: vec![RouteEndpoint::component("test_wrapper/archivist")],
})?
.add_route(CapabilityRoute {
capability: Capability::Event(Event::Stopped, cm_rust::EventMode::Async),
source: RouteEndpoint::component("test_wrapper"),
targets: vec![RouteEndpoint::component("test_wrapper/archivist")],
})?
.add_route(CapabilityRoute {
capability: Capability::Event(Event::Running, cm_rust::EventMode::Async),
source: RouteEndpoint::component("test_wrapper"),
targets: vec![RouteEndpoint::component("test_wrapper/archivist")],
})?
.add_route(CapabilityRoute {
capability: Capability::Event(
Event::capability_ready("diagnostics"),
cm_rust::EventMode::Async,
),
source: RouteEndpoint::component("test_wrapper"),
targets: vec![RouteEndpoint::component("test_wrapper/archivist")],
})?
.add_route(CapabilityRoute {
capability: Capability::Event(
Event::capability_requested("fuchsia.logger.LogSink"),
cm_rust::EventMode::Async,
),
source: RouteEndpoint::component("test_wrapper"),
targets: vec![RouteEndpoint::component("test_wrapper/archivist")],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.test.Suite"),
source: RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH),
targets: vec![RouteEndpoint::AboveRoot],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.hardware.display.Provider"),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.scheduler.ProfileProvider"),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.sysmem.Allocator"),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.tracing.provider.Registry"),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::directory("config-ssl", "", *READ_RIGHTS),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::directory("config-data", "", *READ_RIGHTS),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::directory(
"deprecated-tmp",
"",
*ADMIN_RIGHTS | *READ_WRITE_RIGHTS,
),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::directory("dev-input-report", "", *READ_WRITE_RIGHTS),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?
.add_route(CapabilityRoute {
capability: Capability::directory("dev-display-controller", "", *READ_WRITE_RIGHTS),
source: RouteEndpoint::AboveRoot,
targets: vec![RouteEndpoint::component(WRAPPER_ROOT_REALM_PATH)],
})?;
Ok(builder.build())
}
async fn serve_mocks(
archive_accessor: Arc<fdiagnostics::ArchiveAccessorProxy>,
mock_handles: MockHandles,
) -> Result<(), Error> {
let mut fs = ServiceFs::new();
fs.dir("svc").add_fidl_service(move |stream| {
let archive_accessor_clone = archive_accessor.clone();
fasync::Task::spawn(async move {
diagnostics::run_intermediary_archive_accessor(archive_accessor_clone, stream)
.await
.unwrap_or_else(|e| {
warn!("Couldn't run proxied ArchiveAccessor: {:?}", e);
})
})
.detach()
});
fs.serve_connection(mock_handles.outgoing_dir.into_channel())?;
fs.collect::<()>().await;
Ok(())
}