blob: 024249abfcb9efd5c24a66b8e2669f7662c1dd81 [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 {
anyhow::{Context, Error},
fidl::endpoints::{create_proxy, ClientEnd},
fidl_fuchsia_component_runner::{
ComponentControllerMarker, ComponentRunnerMarker, ComponentStartInfo,
},
fidl_fuchsia_data as fdata, fidl_fuchsia_test as ftest,
fuchsia_component::client::connect_to_service,
futures::TryStreamExt,
runner::component::ComponentNamespace,
std::convert::TryInto,
};
/// Handles a single `ftest::SuiteRequestStream`.
///
/// # Parameters
/// - `test_url`: The URL for the test component to run.
/// - `program`: The program data associated with the runner request for the test component.
/// - `namespace`: The incoming namespace to provide to the test component.
/// - `stream`: The request stream to handle.
pub async fn handle_suite_requests(
test_url: &str,
program: Option<fdata::Dictionary>,
namespace: ComponentNamespace,
mut stream: ftest::SuiteRequestStream,
) -> Result<(), Error> {
while let Some(event) = stream.try_next().await? {
match event {
ftest::SuiteRequest::GetTests { iterator, .. } => {
let stream = iterator.into_stream()?;
handle_case_iterator(test_url, stream).await?;
}
ftest::SuiteRequest::Run { tests, options: _options, listener, .. } => {
let namespace = namespace.clone()?;
let program = program.clone();
run_test_cases(tests, test_url, program, listener, namespace).await?;
}
}
}
Ok(())
}
/// Runs the test cases associated with a single `ftest::SuiteRequest::Run` request.
///
/// Running the test component is delegated to an instance of the starnix runner.
///
/// # 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.
/// - `listener`: The listener for the test run.
/// - `namespace`: The incoming namespace to provide to the test component.
async fn run_test_cases(
tests: Vec<ftest::Invocation>,
test_url: &str,
program: Option<fdata::Dictionary>,
listener: ClientEnd<ftest::RunListenerMarker>,
namespace: ComponentNamespace,
) -> Result<(), Error> {
let run_listener_proxy =
listener.into_proxy().context("Can't convert run listener channel to proxy")?;
for test in tests {
// The run listener expects a socket for the "primary log". This is not connected
// to any actual output of the test component.
let (log_end, _logger) =
fuchsia_zircon::Socket::create(fuchsia_zircon::SocketOpts::empty())?;
let (case_listener_proxy, case_listener) = create_proxy::<ftest::CaseListenerMarker>()?;
run_listener_proxy.on_test_case_started(test, log_end, case_listener)?;
// Connect to the starnix runner to run the test component.
let starnix_runner = connect_to_service::<ComponentRunnerMarker>()?;
let (_, component_controller_server_end) = create_proxy::<ComponentControllerMarker>()?;
let ns = Some(ComponentNamespace::try_into(namespace.clone()?)?);
let start_info = ComponentStartInfo {
resolved_url: Some(test_url.to_string()),
program: program.clone(),
ns,
outgoing_dir: None,
runtime_dir: None,
..ComponentStartInfo::EMPTY
};
starnix_runner.start(start_info, component_controller_server_end)?;
// The result is always set to pass. This should be updated to return a result
// based on whether or not the test component was run successfully.
let result =
ftest::Result_ { status: Some(ftest::Status::Passed), ..ftest::Result_::EMPTY };
case_listener_proxy.finished(result)?;
}
run_listener_proxy.on_finished()?;
Ok(())
}
/// Lists all the available test cases and returns them in response to
/// `ftest::CaseIteratorRequest::GetNext`.
///
/// Currently only one "test case" is returned, named `test_name`.
async fn handle_case_iterator(
test_name: &str,
mut stream: ftest::CaseIteratorRequestStream,
) -> Result<(), Error> {
let mut cases_iter = vec![ftest::Case {
name: Some(test_name.to_string()),
enabled: Some(true),
..ftest::Case::EMPTY
}]
.into_iter();
while let Some(event) = stream.try_next().await? {
match event {
ftest::CaseIteratorRequest::GetNext { responder } => {
responder.send(&mut cases_iter)?;
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use {super::*, fidl::endpoints::create_request_stream, fuchsia_async as fasync};
fn set_up_iterator(test_name: &str) -> ftest::CaseIteratorProxy {
let test_name = test_name.to_string();
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(&test_name, iterator_stream).await;
})
.detach();
iterator_client_end.into_proxy().expect("Failed to create proxy")
}
#[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()));
}
#[fasync::run_singlethreaded(test)]
async fn test_case_enabled() {
let iterator_proxy = set_up_iterator("test");
let result = iterator_proxy.get_next().await.expect("Didn't get first result");
assert_eq!(result[0].enabled, Some(true));
}
}