blob: 5a0db1f6eda7855c0f8666e88801fb6f7b0f6a3d [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 {
anyhow::{format_err, Context as _, Error},
fidl::endpoints::{self, DiscoverableService, ServerEnd},
fidl_fuchsia_component::Error as ComponentError,
fidl_fuchsia_io::{DirectoryMarker, DirectoryProxy},
fidl_fuchsia_sys2 as fsys, fidl_fuchsia_test as ftest,
fidl_fuchsia_test_manager as ftest_manager,
ftest::SuiteMarker,
ftest_manager::{LaunchError, SuiteControllerRequestStream},
fuchsia_async as fasync,
fuchsia_component::client,
futures::prelude::*,
thiserror::Error,
tracing::error,
uuid::Uuid,
};
/// Error encountered running test manager
#[derive(Debug, Error)]
pub enum TestManagerError {
#[error("Error sending response: {:?}", _0)]
Response(fidl::Error),
#[error("Error serving test manager protocol: {:?}", _0)]
Stream(fidl::Error),
#[error("Cannot convert to request stream: {:?}", _0)]
IntoStream(fidl::Error),
}
/// 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 RunningTest::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) => {
responder.send(&mut Err(e)).map_err(TestManagerError::Response)?;
}
}
}
}
}
Ok(())
}
struct RunningTest {
child: Option<fsys::ChildRef>,
}
impl Drop for RunningTest {
fn drop(&mut self) {
let child = self.child.take();
fasync::Task::spawn(async move {
Self::destroy_test(child)
.await
.unwrap_or_else(|error| error!(%error, "cannot destroy test child"));
})
.detach();
}
}
impl RunningTest {
/// Destroy `test_name` and remove it from realm.
async fn destroy_test(child_ref: Option<fsys::ChildRef>) -> Result<(), Error> {
if let Some(mut child_ref) = child_ref {
let realm = client::connect_to_service::<fsys::RealmMarker>()
.context("Cannot connect to realm service")?;
realm
.destroy_child(&mut child_ref)
.await
.context("error calling destroy_child")?
.map_err(|e| format_err!("destroy_child failed: {:?}", e))?;
}
Ok(())
}
/// Serves Suite controller and destroys this test afterwards.
pub async fn serve_controller(
mut self,
mut stream: SuiteControllerRequestStream,
) -> Result<(), Error> {
while let Some(event) = stream.try_next().await? {
match event {
ftest_manager::SuiteControllerRequest::Kill { .. } => {
return Self::destroy_test(self.child.take()).await
}
}
}
Self::destroy_test(self.child.take()).await
}
/// Launch test and return the name of test used to launch it in collection.
pub async fn launch_test(
test_url: &String,
suite_request: ServerEnd<SuiteMarker>,
) -> Result<Self, LaunchError> {
let realm = client::connect_to_service::<fsys::RealmMarker>().map_err(|error| {
error!(%error, "Cannot connect to realm service");
LaunchError::InternalError
})?;
let name = format!("test-{}", Uuid::new_v4().to_string());
let mut collection_ref = fsys::CollectionRef { name: "tests".to_string() };
let child_decl = fsys::ChildDecl {
name: Some(name.clone()),
url: Some(test_url.clone()),
startup: Some(fsys::StartupMode::Lazy),
environment: None,
..fsys::ChildDecl::EMPTY
};
realm
.create_child(&mut collection_ref, child_decl)
.await
.map_err(|error| {
error!(%error, component_url = %test_url, "Failed to call realm to instantiate test component");
LaunchError::InternalError
})?
.map_err(|e| match e {
ComponentError::InvalidArguments
| ComponentError::CollectionNotFound
| ComponentError::InstanceAlreadyExists => {
error!(component_url = %test_url, "Failed to instantiate test component because an instance with the same name already exists (this should not happen)");
LaunchError::InternalError
}
ComponentError::ResourceUnavailable => LaunchError::ResourceUnavailable,
error => {
error!(?error, component_url = %test_url, "Failed to instantiate test component due to an unexpected error");
LaunchError::InternalError
}
})?;
let mut child_ref =
fsys::ChildRef { name: name.clone(), collection: Some("tests".to_string()) };
let (dir, server_end) = endpoints::create_proxy::<DirectoryMarker>().unwrap();
realm
.bind_child(&mut child_ref, server_end)
.await
.map_err(|error| {
error!(%error, component_url = %test_url, "Failed to call realm to bind to test component");
LaunchError::InternalError
})?
.map_err(|e| match e {
ComponentError::InvalidArguments
| ComponentError::InstanceNotFound
| ComponentError::InstanceCannotStart => {
error!(component_url = %test_url, "Test component could not be started");
LaunchError::InternalError
}
ComponentError::InstanceCannotResolve => LaunchError::InstanceCannotResolve,
error => {
error!(?error, component_url = %test_url, "Test component could not be resolved");
LaunchError::InternalError
}
})?;
Self::connect_request_to_protocol_at_dir(&dir, suite_request).map_err(|error| {
error!(
%error,
component_url = %test_url,
"Failed to connect to `fuchsia.test.Suite` protocol"
);
LaunchError::InternalError
})?;
Ok(RunningTest {
child: Some(fsys::ChildRef { name: name, collection: Some("tests".to_string()) }),
})
}
/// Connect to an instance of a FIDL protocol hosted in `directory` to `server_end`.
fn connect_request_to_protocol_at_dir<S: DiscoverableService>(
directory: &DirectoryProxy,
server_end: ServerEnd<S>,
) -> Result<(), Error> {
directory
.open(
fidl_fuchsia_io::OPEN_RIGHT_READABLE | fidl_fuchsia_io::OPEN_RIGHT_WRITABLE,
fidl_fuchsia_io::MODE_TYPE_SERVICE,
S::SERVICE_NAME,
ServerEnd::new(server_end.into_channel()),
)
.context("Failed to open protocol in directory")
}
}