blob: 7f525c624eb36a0d90830277956496147ad5cbb3 [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::{anyhow, format_err, Context, Error},
ffx_core::ffx_plugin,
ffx_test_args::TestCommand,
fidl::endpoints::create_proxy,
fidl_fuchsia_test::{CaseIteratorMarker, Invocation, SuiteProxy},
fidl_fuchsia_test_manager as ftest_manager,
futures::{channel::mpsc, FutureExt, StreamExt},
regex::Regex,
std::io::{stdout, Write},
test_executor::{
run_and_collect_results_for_invocations as run_tests_and_collect, TestEvent, TestResult,
},
};
#[ffx_plugin(ftest_manager::HarnessProxy = "core/appmgr:out:fuchsia.test.manager.Harness")]
pub async fn test(
harness_proxy: ftest_manager::HarnessProxy,
cmd: TestCommand,
) -> Result<(), Error> {
let writer = Box::new(stdout());
if cmd.list {
get_tests(harness_proxy, writer, &cmd.url).await
} else {
run_tests(harness_proxy, writer, &cmd.url, &cmd.tests).await
}
}
async fn get_tests<W: Write>(
harness_proxy: ftest_manager::HarnessProxy,
mut write: W,
suite_url: &String,
) -> Result<(), Error> {
let writer = &mut write;
let (suite_proxy, suite_server_end) = create_proxy().unwrap();
let (_controller_proxy, controller_server_end) = create_proxy().unwrap();
log::info!("launching test suite {}", suite_url);
let _result = harness_proxy
.launch_suite(
&suite_url,
ftest_manager::LaunchOptions {},
suite_server_end,
controller_server_end,
)
.await
.context("launch_suite call failed")?
.map_err(|e| format_err!("error launching test: {:?}", e))?;
let (case_iterator, test_server_end) = create_proxy::<CaseIteratorMarker>()?;
suite_proxy
.get_tests(test_server_end)
.map_err(|e| format_err!("Error getting test steps: {}", e))?;
loop {
let cases = case_iterator.get_next().await?;
if cases.is_empty() {
return Ok(());
}
writeln!(writer, "Tests in suite {}:\n", suite_url)?;
for case in cases {
match case.name {
Some(n) => writeln!(writer, "{}", n)?,
None => writeln!(writer, "<No name>")?,
};
}
}
}
async fn get_invocations(
suite: &SuiteProxy,
test_selector: &Option<Regex>,
) -> Result<Vec<Invocation>, Error> {
let (case_iterator, server_end) = create_proxy()?;
suite.get_tests(server_end).map_err(|e| format_err!("Error getting test steps: {}", e))?;
let mut invocations = Vec::<Invocation>::new();
loop {
let cases = case_iterator.get_next().await?;
if cases.is_empty() {
break;
}
for case in cases {
// TODO: glob type pattern matching would probably be better than regex - maybe
// both? Will update after meeting with UX.
let test_case_name = case.name.unwrap();
match &test_selector {
Some(s) => {
if s.is_match(&test_case_name) {
invocations.push(Invocation { name: Some(test_case_name), tag: None });
}
}
None => invocations.push(Invocation { name: Some(test_case_name), tag: None }),
}
}
}
Ok(invocations)
}
async fn run_tests<W: Write>(
harness_proxy: ftest_manager::HarnessProxy,
mut write: W,
suite_url: &String,
tests: &Option<String>,
) -> Result<(), Error> {
let writer = &mut write;
let (suite_proxy, suite_server_end) = create_proxy().expect("creating suite proxy");
let (_controller_proxy, controller_server_end) =
create_proxy().expect("creating controller proxy");
let test_selector = match tests {
Some(s) => match Regex::new(s) {
Ok(r) => Some(r),
Err(e) => {
return Err(anyhow!("invalid regex for tests: \"{}\"\n{}", s, e));
}
},
None => None,
};
log::info!("launching test suite {}", suite_url);
writeln!(writer, "*** Launching {} ***", suite_url)?;
let _result = harness_proxy
.launch_suite(
&suite_url,
ftest_manager::LaunchOptions {},
suite_server_end,
controller_server_end,
)
.await
.context("launch_test call failed")?
.map_err(|e| format_err!("error launching test: {:?}", e))?;
log::info!("launched suite, getting tests");
let (sender, recv) = mpsc::channel(1);
writeln!(writer, "Getting tests...")?;
let invocations = get_invocations(&suite_proxy, &test_selector).await?;
if invocations.is_empty() {
match tests {
Some(test_selector) => writeln!(writer, "No test cases match {}", test_selector)?,
None => writeln!(writer, "No tests cases found in suite {}", suite_url)?,
};
return Ok(());
}
writeln!(writer, "Running tests...")?;
let (successful_completion, ()) = futures::future::try_join(
collect_events(writer, recv).map(Ok),
run_tests_and_collect(suite_proxy, sender, invocations),
)
.await
.context("running test")?;
if !successful_completion {
return Err(anyhow!("Test run finished prematurely. Something went wrong."));
}
writeln!(writer, "*** Finished {} ***", suite_url)?;
Ok(())
}
async fn collect_events<W: Write>(writer: &mut W, mut recv: mpsc::Receiver<TestEvent>) -> bool {
let mut successful_completion = false;
while let Some(event) = recv.next().await {
match event {
TestEvent::LogMessage { test_case_name, msg } => {
let logs = msg.split("\n");
for log in logs {
if log.len() > 0 {
writeln!(writer, "{}: {}", test_case_name, log.to_string())
.expect("writing to output")
}
}
}
TestEvent::TestCaseStarted { test_case_name } => {
writeln!(writer, "[RUNNING]\t{}", test_case_name).expect("writing to output");
}
TestEvent::TestCaseFinished { test_case_name, result } => {
match result {
TestResult::Passed => {
writeln!(writer, "[PASSED]\t{}", test_case_name).expect("writing to output")
}
TestResult::Failed => {
writeln!(writer, "[FAILED]\t{}", test_case_name).expect("writing to output")
}
TestResult::Skipped => writeln!(writer, "[SKIPPED]\t{}", test_case_name)
.expect("writing to output"),
TestResult::Error => {
writeln!(writer, "[ERROR]\t{}", test_case_name).expect("writing to output")
}
};
}
TestEvent::Finish => {
successful_completion = true;
}
};
}
successful_completion
}
////////////////////////////////////////////////////////////////////////////////
// tests
#[cfg(test)]
mod test {
use {
super::*,
fidl_fuchsia_test::{
Case, CaseIteratorRequest, CaseIteratorRequestStream, CaseListenerMarker, Result_,
Status, SuiteRequest, SuiteRequestStream,
},
fidl_fuchsia_test_manager::{HarnessMarker, HarnessProxy, HarnessRequest},
futures::TryStreamExt,
std::io::BufWriter,
};
fn setup_fake_harness_service_with_tests(num_tests: usize) -> HarnessProxy {
let (proxy, mut stream) =
fidl::endpoints::create_proxy_and_stream::<HarnessMarker>().unwrap();
fuchsia_async::spawn(async move {
while let Ok(Some(req)) = stream.try_next().await {
match req {
HarnessRequest::LaunchSuite {
test_url: _,
suite,
options: _,
controller: _,
responder,
} => {
let suite_request_stream = suite.into_stream().unwrap();
spawn_fake_suite_server(suite_request_stream, num_tests);
let _ = responder.send(&mut Ok(()));
}
}
}
});
proxy
}
fn spawn_fake_suite_server(mut stream: SuiteRequestStream, num_tests: usize) {
fuchsia_async::spawn(async move {
while let Ok(Some(req)) = stream.try_next().await {
match req {
SuiteRequest::GetTests { iterator, control_handle: _ } => {
let values: Vec<String> =
(0..num_tests).map(|i| format!("Test {}", i)).collect();
let iterator_request_stream = iterator.into_stream().unwrap();
spawn_fake_iterator_server(values, iterator_request_stream);
}
SuiteRequest::Run { mut tests, options: _, listener, .. } => {
let listener = listener
.into_proxy()
.context("Can't convert listener into proxy")
.unwrap();
tests.iter_mut().for_each(|t| {
let (log, client_log) =
fidl::Socket::create(fidl::SocketOpts::DATAGRAM)
.context("failed to create socket")
.unwrap();
let (case_listener, client_end) =
create_proxy::<CaseListenerMarker>().unwrap();
listener
.on_test_case_started(
Invocation { name: t.name.take(), tag: None },
client_log,
client_end,
)
.context("Cannot send on_test_case_started")
.unwrap();
log.write(b"Test log message\n").unwrap();
case_listener
.finished(Result_ { status: Some(Status::Passed) })
.context("Cannot send finished")
.unwrap();
});
listener.on_finished().context("Cannot send on_finished event").unwrap();
}
}
}
});
}
fn spawn_fake_iterator_server(values: Vec<String>, mut stream: CaseIteratorRequestStream) {
let mut iter = values.into_iter().map(|name| Case { name: Some(name), enabled: None });
fuchsia_async::spawn(async move {
while let Ok(Some(CaseIteratorRequest::GetNext { responder })) = stream.try_next().await
{
responder.send(&mut iter.by_ref().take(50)).unwrap();
}
});
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_list_tests() {
let mut output = String::new();
let url = "fuchsia-pkg://fuchsia.com/dummy-package#meta/echo_test_realm.cm".to_string();
let num_tests = 50;
let test = Regex::new(r"Test [0-9+]").expect("test regex");
let writer = unsafe { BufWriter::new(output.as_mut_vec()) };
let harness_proxy = setup_fake_harness_service_with_tests(num_tests);
let _response =
get_tests(harness_proxy, writer, &url).await.expect("getting tests should not fail");
assert_eq!(num_tests, test.find_iter(&output).count());
}
async fn test_run(
num_tests: usize,
expected_run: usize,
selector: Option<String>,
) -> Result<(), Error> {
let mut output = String::new();
let url = "fuchsia-pkg://fuchsia.com/dummy-package#meta/echo_test_realm.cm".to_string();
let writer = unsafe { BufWriter::new(output.as_mut_vec()) };
let harness_proxy = setup_fake_harness_service_with_tests(num_tests);
let _response = run_tests(harness_proxy, writer, &url, &selector)
.await
.expect("run tests should not fail");
let test_running = Regex::new(r"RUNNING").expect("test regex");
assert_eq!(expected_run, test_running.find_iter(&output).count());
let test_passed = Regex::new(r"PASSED").expect("test regex");
assert_eq!(expected_run, test_passed.find_iter(&output).count());
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_run_tests() -> Result<(), Error> {
test_run(100, 100, None).await
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_run_tests_with_selector() -> Result<(), Error> {
test_run(100, 19, Some("6".to_string())).await
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_run_tests_with_unmatched_selector() -> Result<(), Error> {
test_run(100, 0, Some("Echo".to_string())).await
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_run_tests_with_invalid_selector() -> Result<(), Error> {
let mut output = String::new();
let url = "fuchsia-pkg://fuchsia.com/dummy-package#meta/echo_test_realm.cm".to_string();
let selector = Some("[".to_string());
let num_tests = 1;
let mut writer = unsafe { BufWriter::new(output.as_mut_vec()) };
let harness_proxy = setup_fake_harness_service_with_tests(num_tests);
let response = run_tests(harness_proxy, &mut writer, &url, &selector).await;
assert!(response.is_err());
Ok(())
}
}