blob: ab871cf549f917ea614ccdd71b77050384864c11 [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.
//! Shared traits and methods to be used in test suite servers for tests that are executed as ELF
//! components.
use {
crate::{
cases::TestCaseInfo,
elf::Component,
errors::{EnumerationError, RunTestError},
},
async_trait::async_trait,
fidl_fuchsia_test as ftest, fuchsia_async as fasync, fuchsia_zircon as zx,
fuchsia_zircon_sys::ZX_CHANNEL_MAX_MSG_BYTES,
futures::{
future::{AbortHandle, Shared},
lock::Mutex,
prelude::*,
},
log::{error, warn},
rust_measure_tape_for_case::measure,
std::{
pin::Pin,
sync::{Arc, Weak},
},
thiserror::Error,
};
/// A pinned, boxed future whose output is `Result<T, E>`.
pub type PinnedFuture<T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send>>;
/// `SharedFuture` wrapper around `PinnedFuture<T, E>`. Can be cloned.
type SharedFuture<T, E> = Shared<PinnedFuture<T, E>>;
/// A mutable container around `SharedFuture<T, E>` that can be filled in when the stored future is
/// first created.
pub type MemoizedFutureContainer<T, E> = Arc<Mutex<Option<SharedFuture<T, E>>>>;
/// Ordered list of `TestCaseInfo`s.
pub type EnumeratedTestCases = Arc<Vec<TestCaseInfo>>;
/// Describes a test suite server for tests that are executed as ELF components.
#[async_trait]
pub trait SuiteServer: Sized + Sync + Send {
/// Run this server.
///
/// * `component`: Test component instance.
/// * `test_url`: URL of test component.
/// * `stream`: Stream to serve Suite protocol on.
///
/// Returns abortable handle for suite server future.
fn run(
self,
component: Weak<Component>,
test_url: &str,
stream: ftest::SuiteRequestStream,
) -> AbortHandle;
/// Retrieves test information from the test binary.
///
/// A cached list of test cases should be returned by cloning a
/// `SharedFuture<EnumeratedTestCases, EnumerationError>` that is stored in the suite server
/// struct.
async fn enumerate_tests(
&self,
test_component: Arc<Component>,
) -> Result<EnumeratedTestCases, EnumerationError>;
/// Runs requested tests and sends test events to the given listener.
async fn run_tests(
&self,
invocations: Vec<ftest::Invocation>,
run_options: ftest::RunOptions,
test_component: Arc<Component>,
run_listener: &ftest::RunListenerProxy,
) -> Result<(), RunTestError>;
fn get_parallel_count(parallel: u16) -> usize {
if parallel == 0 {
warn!("Client passed number of concurrent tests as 0, setting it to 1.");
return 1;
}
parallel.into()
}
/// Implements `fuchsia.test.Suite` service and runs test.
async fn serve_test_suite(
mut self,
mut stream: ftest::SuiteRequestStream,
component: Weak<Component>,
) -> Result<(), SuiteServerError> {
while let Some(event) = stream.try_next().await.map_err(SuiteServerError::Stream)? {
match event {
ftest::SuiteRequest::GetTests { iterator, control_handle: _ } => {
let component = component.upgrade();
if component.is_none() {
// no component object, return, test has ended.
break;
}
let mut stream = iterator.into_stream().map_err(SuiteServerError::Stream)?;
let tests = self.enumerate_tests(component.unwrap()).await?;
fasync::Task::spawn(
async move {
let mut iter =
tests.iter().map(|TestCaseInfo { name, enabled }| ftest::Case {
name: Some(name.clone()),
enabled: Some(*enabled),
..ftest::Case::EMPTY
});
while let Some(ftest::CaseIteratorRequest::GetNext { responder }) =
stream.try_next().await?
{
// Paginate cases
// Page overhead of message header + vector
let mut bytes_used: usize = 32;
let mut case_count = 0;
for case in iter.clone() {
bytes_used += measure(&case).num_bytes;
if bytes_used > ZX_CHANNEL_MAX_MSG_BYTES as usize {
break;
}
case_count += 1;
}
responder
.send(&mut iter.by_ref().take(case_count))
.map_err(SuiteServerError::Response)?;
}
Ok(())
}
.unwrap_or_else(|e: anyhow::Error| error!("error serving tests: {:?}", e)),
)
.detach();
}
ftest::SuiteRequest::Run { tests, options, listener, .. } => {
let component = component.upgrade();
if component.is_none() {
// no component object, return, test has ended.
break;
}
let listener =
listener.into_proxy().map_err(FidlError::ClientEndToProxy).unwrap();
self.run_tests(tests, options, component.unwrap(), &listener).await?;
listener.on_finished().map_err(RunTestError::SendFinishAllTests).unwrap();
}
}
}
Ok(())
}
}
/// Error encountered while running suite server
#[derive(Debug, Error)]
pub enum SuiteServerError {
#[error("test enumeration failed: {:?}", _0)]
Enumeration(crate::errors::EnumerationError),
#[error("error running test: {:?}", _0)]
RunTest(crate::errors::RunTestError),
#[error("stream failed: {:?}", _0)]
Stream(fidl::Error),
#[error("Cannot send fidl response: {:?}", _0)]
Response(fidl::Error),
}
impl From<EnumerationError> for SuiteServerError {
fn from(error: crate::errors::EnumerationError) -> Self {
SuiteServerError::Enumeration(error)
}
}
impl From<RunTestError> for SuiteServerError {
fn from(error: crate::errors::RunTestError) -> Self {
SuiteServerError::RunTest(error)
}
}
/// Error encountered while working with the FIDL library.
#[derive(Debug, Error)]
pub enum FidlError {
#[error("cannot convert proxy to channel")]
ProxyToChannel,
#[error("cannot convert client end to proxy: {:?}", _0)]
ClientEndToProxy(fidl::Error),
#[error("cannot create fidl proxy: {:?}", _0)]
CreateProxy(fidl::Error),
}
/// Error encountered while working with kernel object.
#[derive(Debug, Error)]
pub enum KernelError {
#[error("job creation failed: {:?}", _0)]
CreateJob(zx::Status),
#[error("error waiting for test process to exit: {:?}", _0)]
ProcessExit(zx::Status),
#[error("error getting info from process: {:?}", _0)]
ProcessInfo(zx::Status),
#[error("error creating socket: {:?}", _0)]
CreateSocket(zx::Status),
#[error("cannot convert zircon socket to async socket: {:?}", _0)]
SocketToAsync(zx::Status),
}