blob: 609be18e36e8d61b2c2096c23517b7b4164a220c [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.
use {
async_trait::async_trait,
fidl_fuchsia_test::{
self as ftest, Invocation, Result_ as TestResult, RunListenerProxy, Status,
},
fuchsia_async as fasync, fuchsia_zircon as zx,
futures::{
future::{abortable, AbortHandle},
prelude::*,
TryStreamExt,
},
log::{debug, error},
std::sync::{Arc, Weak},
test_runners_lib::{
cases::TestCaseInfo,
elf::{Component, EnumeratedTestCases, FidlError, KernelError, SuiteServer},
errors::*,
launch,
logs::{buffer_and_drain_logger, LogWriter, LoggerStream},
},
};
/// Implements `fuchsia.test.Suite` and runs provided test.
#[derive(Default)]
pub struct TestServer {}
static PARALLEL_DEFAULT: u16 = 1;
#[async_trait]
impl SuiteServer for TestServer {
/// Launches test process and gets test list out. Returns list of tests names in the format
/// defined by gtests, i.e FOO.Bar.
/// It only runs enumeration logic once, caches and returns the same result back on subsequent
/// calls.
async fn enumerate_tests(
&self,
_test_component: Arc<Component>,
) -> Result<EnumeratedTestCases, EnumerationError> {
Ok(Arc::new(vec![TestCaseInfo { name: "main".to_string(), enabled: true }]))
}
async fn run_tests(
&self,
invocations: Vec<Invocation>,
run_options: ftest::RunOptions,
component: Arc<Component>,
run_listener: &RunListenerProxy,
) -> Result<(), RunTestError> {
let num_parallel =
Self::get_parallel_count(run_options.parallel.unwrap_or(PARALLEL_DEFAULT));
let invocations = stream::iter(invocations);
invocations
.map(Ok)
.try_for_each_concurrent(num_parallel, |invocation| {
self.run_test(invocation, &run_options, component.clone(), run_listener)
})
.await
}
/// Run this server.
fn run(
self,
weak_component: Weak<Component>,
test_url: &str,
stream: ftest::SuiteRequestStream,
) -> AbortHandle {
let test_url = test_url.clone().to_owned();
let (fut, test_suite_abortable_handle) =
abortable(self.serve_test_suite(stream, weak_component.clone()));
fasync::Task::local(async move {
match fut.await {
Ok(result) => {
if let Err(e) = result {
error!("server failed for test {}: {:?}", test_url, e);
}
}
Err(e) => debug!("server aborted for test {}: {:?}", test_url, e),
}
debug!("Done running server for {}.", test_url);
})
.detach();
test_suite_abortable_handle
}
}
impl TestServer {
pub fn new() -> Self {
Self {}
}
pub fn validate_args(_args: &Vec<String>) -> Result<(), ArgumentError> {
// Unopinionated about args,
// they're passed through to the test program unfiltered
Ok(())
}
async fn run_test<'a>(
&'a self,
invocation: Invocation,
run_options: &ftest::RunOptions,
component: Arc<Component>,
run_listener: &RunListenerProxy,
) -> Result<(), RunTestError> {
if "main" != invocation.name.as_ref().ok_or(RunTestError::TestCaseName)?.to_string() {
// "main" is the only valid test case name
return Ok(());
}
let (test_logger, log_client) =
zx::Socket::create(zx::SocketOpts::STREAM).map_err(KernelError::CreateSocket).unwrap();
let (case_listener_proxy, listener) =
fidl::endpoints::create_proxy::<fidl_fuchsia_test::CaseListenerMarker>()
.map_err(FidlError::CreateProxy)
.unwrap();
run_listener
.on_test_case_started(invocation, log_client, listener)
.map_err(RunTestError::SendStart)?;
let test_logger =
fasync::Socket::from_socket(test_logger).map_err(KernelError::SocketToAsync).unwrap();
let mut test_logger = LogWriter::new(test_logger);
let mut args = component.args.clone();
if let Some(user_args) = &run_options.arguments {
args.extend(user_args.clone());
}
// Launch test program
let (process, _job, stdlogger) =
launch_component_process::<RunTestError>(&component, args).await?;
// Drain stdout
buffer_and_drain_logger(stdlogger, &mut test_logger).await?;
// Wait for test to return
fasync::OnSignals::new(&process, zx::Signals::PROCESS_TERMINATED)
.await
.map_err(KernelError::ProcessExit)
.unwrap();
let process_info = process.info().map_err(RunTestError::ProcessInfo)?;
// Map return value of zero to Passed, non-zero to Failed
let status = match process_info.return_code {
0 => Status::Passed,
_ => Status::Failed,
};
case_listener_proxy
.finished(TestResult { status: Some(status), ..TestResult::EMPTY })
.map_err(RunTestError::SendFinish)?;
Ok(())
}
}
/// Convenience wrapper around [`launch::launch_process`].
async fn launch_component_process<E>(
component: &Component,
args: Vec<String>,
) -> Result<(zx::Process, launch::ScopedJob, LoggerStream), E>
where
E: From<NamespaceError> + From<launch::LaunchError>,
{
Ok(launch::launch_process(launch::LaunchProcessArgs {
bin_path: &component.binary,
process_name: &component.name,
job: Some(component.job.create_child_job().map_err(KernelError::CreateJob).unwrap()),
ns: component.ns.clone().map_err(NamespaceError::Clone)?,
args: Some(args),
name_infos: None,
environs: None,
handle_infos: None,
})
.await?)
}