blob: 3daff4065060939e613e6eea9272b6a720adec04 [file] [log] [blame]
// Copyright 2021 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::binder_latency::run_binder_latency,
crate::gbenchmark::*,
crate::gtest::*,
crate::helpers::*,
crate::ltp::*,
anyhow::{anyhow, Context, Error},
fidl::endpoints::create_proxy,
fidl_fuchsia_component_runner as frunner, fidl_fuchsia_data as fdata,
fidl_fuchsia_test::{self as ftest},
frunner::ComponentStartInfo,
frunner::{ComponentRunnerMarker, ComponentRunnerProxy},
fuchsia_zircon::sys::ZX_CHANNEL_MAX_MSG_BYTES,
futures::TryStreamExt,
namespace::Namespace,
rust_measure_tape_for_case::Measurable as _,
test_runners_lib::elf::SuiteServerError,
tracing::debug,
};
/// Determines what type of tests the program is.
fn get_test_type(program: &fdata::Dictionary) -> Result<TestType, Error> {
match get_opt_str_value_from_dict(program, "test_type")?.as_deref() {
Some("binder_latency") => Ok(TestType::BinderLatency),
Some("gbenchmark") => Ok(TestType::Gbenchmark),
Some("gtest") => Ok(TestType::Gtest),
Some("gtest_xml_output") => Ok(TestType::GtestXmlOutput),
Some("gunit") => Ok(TestType::Gunit),
Some("ltp") => Ok(TestType::Ltp),
Some(value) => Err(anyhow!("Unrecognized test_type: {}", value)),
// If test_type is not specified, then just run the component as a single test case.
None => Ok(TestType::SingleTest),
}
}
async fn component_runner_from_start_info(
start_info: ComponentStartInfo,
) -> Result<ComponentRunnerProxy, Error> {
debug!("use component runner from namespace");
let ns = start_info.ns.ok_or_else(|| anyhow!("start info does not have namespace"))?;
let ns = Namespace::try_from(ns)?;
let svc = ns
.get(&"/svc".parse().unwrap())
.ok_or_else(|| anyhow!("test component namespace does not have /svc"))?;
return Ok(
fuchsia_component::client::connect_to_protocol_at_dir_root::<ComponentRunnerMarker>(svc)?,
);
}
/// Handles a single `ftest::SuiteRequestStream`.
///
/// # Parameters
/// - `start_info`: The start info to use when running the test component.
/// - `stream`: The request stream to handle.
pub async fn handle_suite_requests(
mut start_info: frunner::ComponentStartInfo,
mut stream: ftest::SuiteRequestStream,
) -> Result<(), Error> {
debug!(?start_info, "got suite request stream");
let test_type = get_test_type(start_info.program.as_ref().unwrap())?;
// The kernel start info is largely the same as that of the test component. The main difference
// is that the `container_start_info` does not contain the outgoing directory of the test component.
let container_start_info = clone_start_info(&mut start_info)?;
let component_runner = component_runner_from_start_info(container_start_info).await?;
while let Some(event) = stream.try_next().await? {
let mut test_start_info = clone_start_info(&mut start_info)?;
debug!("got suite request");
match event {
ftest::SuiteRequest::GetTests { iterator, .. } => {
debug!("enumerating test cases");
let stream = iterator.into_stream()?;
let test_cases = match test_type {
TestType::Gtest | TestType::Gunit | TestType::GtestXmlOutput => {
get_cases_list_for_gtests(test_start_info, &component_runner, test_type)
.await?
}
TestType::Ltp => get_cases_list_for_ltp(test_start_info).await?,
TestType::BinderLatency | TestType::Gbenchmark | TestType::SingleTest => {
let name = test_start_info
.resolved_url
.as_ref()
.ok_or(anyhow!("Missing resolved URL"))?;
vec![ftest::Case {
name: Some(name.clone()),
enabled: Some(true),
..Default::default()
}]
}
};
handle_case_iterator(test_cases, stream).await?
}
ftest::SuiteRequest::Run { tests, options, listener, .. } => {
debug!(?tests, "running tests");
let run_listener_proxy =
listener.into_proxy().context("Can't convert run listener channel to proxy")?;
if tests.is_empty() {
debug!("no tests listed, returning");
run_listener_proxy.on_finished()?;
break;
}
// Replace tests with program arguments if they were passed in.
let mut program =
test_start_info.program.clone().ok_or(anyhow!("Missing program."))?;
if let Some(test_args) = options.arguments {
replace_program_args(test_args, &mut program);
}
test_start_info.program = Some(program);
debug!(?test_start_info, "running tests with info");
match test_type {
TestType::Gunit => {
run_gunit_cases(
tests,
test_start_info,
&run_listener_proxy,
&component_runner,
)
.await?;
}
TestType::Gtest | TestType::GtestXmlOutput => {
run_gtest_cases(
tests,
test_start_info,
&run_listener_proxy,
&component_runner,
test_type,
)
.await?;
}
TestType::Gbenchmark => {
run_gbenchmark(
tests.get(0).unwrap().clone(),
test_start_info,
&run_listener_proxy,
&component_runner,
)
.await?
}
TestType::BinderLatency => {
run_binder_latency(
tests.get(0).unwrap().clone(),
test_start_info,
&run_listener_proxy,
&component_runner,
)
.await?
}
TestType::Ltp => {
run_ltp_cases(
tests,
test_start_info,
&run_listener_proxy,
&component_runner,
)
.await?
}
TestType::SingleTest => {
run_test_case(
tests.get(0).unwrap().clone(),
test_start_info,
&run_listener_proxy,
&component_runner,
)
.await?
}
}
run_listener_proxy.on_finished()?;
}
}
}
Ok(())
}
/// Runs a test case associated with a single `ftest::SuiteRequest::Run` request.
///
/// Running the test component is delegated to an instance of the starnix kernel.
///
/// # Parameters
/// - `tests`: The tests that are to be run. Each test executes an independent run of the test
/// component.
/// - `test_url`: The URL of the test component.
/// - `program`: The program data associated with the runner request for the test component.
/// - `run_listener_proxy`: The listener proxy for the test run.
/// - `component_runner`: The runner that will run the test component.
async fn run_test_case(
test: ftest::Invocation,
mut start_info: frunner::ComponentStartInfo,
run_listener_proxy: &ftest::RunListenerProxy,
component_runner: &frunner::ComponentRunnerProxy,
) -> Result<(), Error> {
debug!("running generic fallback test suite");
let (case_listener_proxy, case_listener) = create_proxy::<ftest::CaseListenerMarker>()?;
let (numbered_handles, std_handles) = create_numbered_handles();
start_info.numbered_handles = numbered_handles;
debug!("notifying client test case started");
run_listener_proxy.on_test_case_started(&test, std_handles, case_listener)?;
debug!("starting test component");
let component_controller = start_test_component(start_info, component_runner)?;
let result = read_result(component_controller.take_event_stream()).await;
debug!(?result, "notifying client test case finished");
case_listener_proxy.finished(&result)?;
Ok(())
}
/// Lists all the available test cases and returns them in response to
/// `ftest::CaseIteratorRequest::GetNext`.
async fn handle_case_iterator(
cases: Vec<ftest::Case>,
mut stream: ftest::CaseIteratorRequestStream,
) -> Result<(), Error> {
let mut remaining_cases = &cases[..];
while let Some(event) = stream.try_next().await? {
match event {
ftest::CaseIteratorRequest::GetNext { responder } => {
// Paginate cases
// Page overhead of message header + vector
let mut bytes_used: usize = 32;
let mut case_count = 0;
for case in remaining_cases {
bytes_used += case.measure().num_bytes;
if bytes_used > ZX_CHANNEL_MAX_MSG_BYTES as usize {
break;
}
case_count += 1;
}
responder
.send(&remaining_cases[..case_count])
.map_err(SuiteServerError::Response)?;
remaining_cases = &remaining_cases[case_count..];
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl::endpoints::{create_request_stream, ClientEnd},
fuchsia_async as fasync, fuchsia_zircon as zx,
};
/// Returns a `ftest::CaseIteratorProxy` that is served by `super::handle_case_iterator`.
///
/// # Parameters
/// - `test_name`: The name of the test case that is provided to `handle_case_iterator`.
fn set_up_iterator(test_name: &str) -> ftest::CaseIteratorProxy {
let cases = vec![ftest::Case { name: Some(test_name.to_string()), ..Default::default() }];
let (iterator_client_end, iterator_stream) =
create_request_stream::<ftest::CaseIteratorMarker>()
.expect("Couldn't create case iterator");
fasync::Task::local(async move {
let _ = handle_case_iterator(cases, iterator_stream).await;
})
.detach();
iterator_client_end.into_proxy().expect("Failed to create proxy")
}
/// Spawns a `ComponentRunnerRequestStream` server that immediately closes all incoming
/// component controllers with the epitaph specified in `component_controller_epitaph`.
///
/// This function can be used to mock the starnix kernel in a way that simulates a component
/// exiting with or without error.
///
/// # Parameters
/// - `component_controller_epitaph`: The epitaph used to close the component controller.
///
/// # Returns
/// A `ComponentRunnerProxy` that serves each run request by closing the component with the
/// provided epitaph.
fn spawn_runner(component_controller_epitaph: zx::Status) -> frunner::ComponentRunnerProxy {
let (proxy, mut request_stream) =
fidl::endpoints::create_proxy_and_stream::<frunner::ComponentRunnerMarker>().unwrap();
fasync::Task::local(async move {
while let Some(event) =
request_stream.try_next().await.expect("Error in test runner request stream")
{
match event {
frunner::ComponentRunnerRequest::Start {
start_info: _start_info,
controller,
..
} => {
controller
.close_with_epitaph(component_controller_epitaph)
.expect("Could not close with epitaph");
}
}
}
})
.detach();
proxy
}
/// Returns the status from the first test case reported to `run_listener_stream`.
///
/// This is done by listening to the first `CaseListener` provided via `OnTestCaseStarted`.
///
/// # Parameters
/// - `run_listener_stream`: The run listener stream to extract the test status from.
///
/// # Returns
/// The status of the first test case that is run, or `None` if no such status is reported.
async fn listen_to_test_result(
mut run_listener_stream: ftest::RunListenerRequestStream,
) -> Option<ftest::Status> {
match run_listener_stream.try_next().await.expect("..") {
Some(ftest::RunListenerRequest::OnTestCaseStarted {
invocation: _,
std_handles: _,
listener,
..
}) => match listener
.into_stream()
.expect("Failed to get case listener stream")
.try_next()
.await
.expect("Failed to get case listener stream request")
{
Some(ftest::CaseListenerRequest::Finished { result, .. }) => result.status,
_ => None,
},
_ => None,
}
}
/// Spawns a task that calls `super::run_test_cases` with the provided `run_listener` and
/// `runner_proxy`. The call is made with a mock test case.
fn spawn_run_test_cases(
run_listener: ClientEnd<ftest::RunListenerMarker>,
component_runner: frunner::ComponentRunnerProxy,
) {
fasync::Task::local(async move {
let _ = run_test_case(
ftest::Invocation {
name: Some("".to_string()),
tag: Some("".to_string()),
..Default::default()
},
frunner::ComponentStartInfo { ns: Some(vec![]), ..Default::default() },
&run_listener.into_proxy().expect("Couldn't create proxy."),
&component_runner,
)
.await;
})
.detach();
}
#[fasync::run_singlethreaded(test)]
async fn test_number_of_test_cases() {
let iterator_proxy = set_up_iterator("test");
let first_result = iterator_proxy.get_next().await.expect("Didn't get first result");
let second_result = iterator_proxy.get_next().await.expect("Didn't get second result");
assert_eq!(first_result.len(), 1);
assert_eq!(second_result.len(), 0);
}
#[fasync::run_singlethreaded(test)]
async fn test_case_name() {
let test_name = "test_name";
let iterator_proxy = set_up_iterator(test_name);
let result = iterator_proxy.get_next().await.expect("Didn't get first result");
assert_eq!(result[0].name, Some(test_name.to_string()));
}
/// Tests that when starnix closes the component controller with an `OK` status, the test case
/// passes.
#[fasync::run_singlethreaded(test)]
async fn test_component_controller_epitaph_ok() {
let component_runner = spawn_runner(zx::Status::OK);
let (run_listener, run_listener_stream) =
create_request_stream::<ftest::RunListenerMarker>()
.expect("Couldn't create case listener");
spawn_run_test_cases(run_listener, component_runner);
assert_eq!(listen_to_test_result(run_listener_stream).await, Some(ftest::Status::Passed));
}
/// Tests that when starnix closes the component controller with an error status, the test case
/// fails.
#[fasync::run_singlethreaded(test)]
async fn test_component_controller_epitaph_not_ok() {
let component_runner = spawn_runner(zx::Status::INTERNAL);
let (run_listener, run_listener_stream) =
create_request_stream::<ftest::RunListenerMarker>()
.expect("Couldn't create case listener");
spawn_run_test_cases(run_listener, component_runner);
assert_eq!(listen_to_test_result(run_listener_stream).await, Some(ftest::Status::Failed));
}
}