blob: d0663c019077629dcfc82fc2a72ab3a1741a1d3f [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 as ftest,
ftest::{Invocation, RunListenerProxy},
fuchsia_async as fasync, fuchsia_zircon as zx,
futures::{
future::{abortable, AbortHandle, FutureExt as _},
lock::Mutex,
prelude::*,
},
lazy_static::lazy_static,
log::{debug, error},
regex::Regex,
std::{
collections::HashSet,
str::from_utf8,
sync::{Arc, Weak},
},
test_runners_lib::{
cases::TestCaseInfo,
elf::{
Component, EnumeratedTestCases, FidlError, KernelError, MemoizedFutureContainer,
PinnedFuture, SuiteServer,
},
errors::*,
launch,
logs::{buffer_and_drain_logger, LogStreamReader, LogWriter, LoggerStream},
},
};
type EnumeratedTestNames = Arc<HashSet<String>>;
/// Implements `fuchsia.test.Suite` and runs provided test.
pub struct TestServer {
/// Cache to store enumerated tests.
tests_future_container: MemoizedFutureContainer<EnumeratedTestCases, EnumerationError>,
/// Index of disabled tests for faster membership checking.
disabled_tests_future_container: MemoizedFutureContainer<EnumeratedTestNames, EnumerationError>,
}
/// Default concurrency for running test cases in parallel.
static PARALLEL_DEFAULT: u16 = 10;
#[async_trait]
impl SuiteServer for TestServer {
/// Launches a process that lists the tests without actually running any of them. It then parses
/// the output of that process into a vector of strings.
///
/// Example output for rust test process:
///
/// ```text
/// tests::purposefully_failing_test: test
/// tests::test_full_path: test
/// tests::test_minimal_path: test
///
/// 3 tests, 0 benchmarks
/// ```
///
/// The list of tests is cached.
async fn enumerate_tests(
&self,
test_component: Arc<Component>,
) -> Result<EnumeratedTestCases, EnumerationError> {
self.tests(test_component).await
}
async fn run_tests(
&self,
invocations: Vec<Invocation>,
run_options: ftest::RunOptions,
test_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| async {
let test = invocation.name.as_ref().ok_or(RunTestError::TestCaseName)?.to_string();
debug!("Running test {}", test);
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();
let test_logger = fasync::Socket::from_socket(test_logger)
.map_err(KernelError::SocketToAsync)
.unwrap();
run_listener
.on_test_case_started(invocation, log_client, listener)
.map_err(RunTestError::SendStart)?;
let mut test_logger = LogWriter::new(test_logger);
match self
.run_test(&test, &run_options, test_component.clone(), &mut test_logger)
.await
{
Ok(result) => {
case_listener_proxy.finished(result).map_err(RunTestError::SendFinish)?;
}
Err(error) => {
error!("failed to run test '{}'. {}", test, error);
case_listener_proxy
.finished(ftest::Result_ {
status: Some(ftest::Status::Failed),
..ftest::Result_::EMPTY
})
.map_err(RunTestError::SendFinish)?;
}
}
return Ok::<(), RunTestError>(());
})
.await
}
/// Run this server.
fn run(
self,
weak_test_component: Weak<Component>,
test_url: &str,
request_stream: fidl_fuchsia_test::SuiteRequestStream,
) -> AbortHandle {
let test_url = test_url.clone().to_owned();
let (fut, test_suite_abortable_handle) =
abortable(self.serve_test_suite(request_stream, weak_test_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) => error!("server aborted for test {}: {:?}", test_url, e),
}
debug!("Done running server for {}.", test_url);
})
.detach();
test_suite_abortable_handle
}
}
lazy_static! {
static ref RESTRICTED_FLAGS: HashSet<&'static str> =
vec!["--nocapture", "--list"].into_iter().collect();
}
impl TestServer {
/// Creates new test server.
/// Clients should call this function to create new object and then call `serve_test_suite`.
pub fn new() -> Self {
Self {
tests_future_container: Arc::new(Mutex::new(None)),
disabled_tests_future_container: Arc::new(Mutex::new(None)),
}
}
/// Retrieves and memoizes the full list of tests from the test binary.
///
/// The entire `Future` is memoized, so repeated calls do not execute the test binary
/// repeatedly.
///
/// This outer method is _not_ `async`, to avoid capturing a reference to `&self` and fighting
/// the borrow checker until the end of time.
fn tests(
&self,
test_component: Arc<Component>,
) -> impl Future<Output = Result<EnumeratedTestCases, EnumerationError>> {
/// Fetches the full list of tests from the test binary.
///
/// The `disabled_tests_future` is passed in to determine which tests should be marked
/// disabled.
async fn fetch(
test_component: Arc<Component>,
disabled_tests_future: impl Future<Output = Result<EnumeratedTestNames, EnumerationError>>
+ Send
+ 'static,
) -> Result<EnumeratedTestCases, EnumerationError> {
let test_names = get_tests(test_component, TestFilter::AllTests).await?;
let disabled_tests = disabled_tests_future.await?;
let tests: Vec<TestCaseInfo> = test_names
.into_iter()
.map(|name| {
let enabled = !disabled_tests.contains(&name);
TestCaseInfo { name, enabled }
})
.collect();
Ok(Arc::new(tests))
}
/// Populates the given `tests_future_container` with a future, or returns a copy of that
/// future if already present.
async fn get_or_insert_tests_future(
test_component: Arc<Component>,
tests_future_container: MemoizedFutureContainer<EnumeratedTestCases, EnumerationError>,
disabled_tests_future: impl Future<Output = Result<EnumeratedTestNames, EnumerationError>>
+ Send
+ 'static,
) -> Result<EnumeratedTestCases, EnumerationError> {
tests_future_container
.lock()
.await
.get_or_insert_with(|| {
// The type must be specified in order to compile.
let fetched: PinnedFuture<EnumeratedTestCases, EnumerationError> =
Box::pin(fetch(test_component, disabled_tests_future));
fetched.shared()
})
// This clones the `SharedFuture`.
.clone()
.await
}
let tests_future_container = self.tests_future_container.clone();
let disabled_tests_future = self.disabled_tests(test_component.clone());
get_or_insert_tests_future(test_component, tests_future_container, disabled_tests_future)
}
/// Retrieves and memoizes the list of just the disabled tests from the test binary.
///
/// This outer method is _not_ `async`, to avoid capturing a reference to `&self` and fighting
/// the borrow checker until the end of time.
fn disabled_tests(
&self,
test_component: Arc<Component>,
) -> impl Future<Output = Result<EnumeratedTestNames, EnumerationError>> {
type DisabledTestsFutureContainer =
MemoizedFutureContainer<EnumeratedTestNames, EnumerationError>;
/// Fetches the list of disabled tests from the test binary.
async fn fetch(
test_component: Arc<Component>,
) -> Result<EnumeratedTestNames, EnumerationError> {
let disabled_tests = get_tests(test_component, TestFilter::DisabledTests)
.await?
.into_iter()
.collect::<HashSet<String>>();
Ok(Arc::new(disabled_tests))
}
/// Populates the given `disabled_tests_future_container` with a future, or returns a copy
/// of that future if already present.
async fn get_or_insert_disabled_tests_future(
test_component: Arc<Component>,
disabled_tests_future_container: DisabledTestsFutureContainer,
) -> Result<EnumeratedTestNames, EnumerationError> {
disabled_tests_future_container
.lock()
.await
.get_or_insert_with(|| {
// The type must be specified.
let fetched: PinnedFuture<EnumeratedTestNames, EnumerationError> =
Box::pin(fetch(test_component));
fetched.shared()
})
.clone()
.await
}
let disabled_tests_future_container = self.disabled_tests_future_container.clone();
get_or_insert_disabled_tests_future(test_component, disabled_tests_future_container)
}
/// Returns `true` if the given test is disabled (marked `#[ignore]`) by the developer.
///
/// If the set of disabled tests isn't yet cached, this will retrieve it -- hence `async`.
async fn is_test_disabled<'a>(
&'a self,
test_component: Arc<Component>,
test_name: &str,
) -> Result<bool, EnumerationError> {
let disabled_tests = self.disabled_tests(test_component).await?;
Ok(disabled_tests.contains(test_name))
}
#[cfg(rust_panic = "unwind")]
async fn run_test(
&self,
_test: &str,
_test_component: &Component,
_test_logger: &mut LogWriter,
) -> Result<ftest::Result_, RunTestError> {
// this will go away soon, so no use of supporting it when we can't
// even test this code.
panic!("not supported");
}
/// Launches a process that actually runs the test and parses the resulting JSON output.
///
/// The mechanism by which Rust tests are launched in individual processes ignores whether a
/// particular test was marked `#[ignore]`, so this method preemptively checks whether a
/// the given test is disabled and returns early if the test should be skipped.
#[cfg(rust_panic = "abort")]
async fn run_test<'a>(
&'a self,
test: &str,
run_options: &ftest::RunOptions,
test_component: Arc<Component>,
test_logger: &mut LogWriter,
) -> Result<ftest::Result_, RunTestError> {
// Exit codes used by Rust's libtest runner.
const TR_OK: i64 = 50;
const TR_FAILED: i64 = 51;
// Rust test binaries launched with `__RUST_TEST_INVOKE` don't care if a test is disabled,
// so we must manually return early in order to skip a test.
let skip_disabled_tests = !run_options.include_disabled_tests.unwrap_or(false);
if skip_disabled_tests && self.is_test_disabled(test_component.clone(), test).await? {
return Ok(ftest::Result_ {
status: Some(ftest::Status::Skipped),
..ftest::Result_::EMPTY
});
}
let test_invoke = Some(format!("__RUST_TEST_INVOKE={}", test));
let mut args = vec![
// Disable stdout capture in the Rust test harness
// so we can capture it ourselves
"--nocapture".to_owned(),
// fxbug.dev(66860): Don't print in color
"--color".to_owned(),
"never".to_owned(),
];
args.extend(test_component.args.clone());
if let Some(user_args) = &run_options.arguments {
if let Err(e) = Self::validate_args(&user_args) {
test_logger.write_str(&format!("{}", e)).await?;
return Ok(ftest::Result_ {
status: Some(ftest::Status::Failed),
..ftest::Result_::EMPTY
});
}
args.extend(user_args.clone());
}
// run test.
// Load bearing to hold job guard.
let (process, _job, stdlogger) =
launch_component_process::<RunTestError>(&test_component, args, test_invoke).await?;
buffer_and_drain_logger(stdlogger, test_logger).await?;
fasync::OnSignals::new(&process, zx::Signals::PROCESS_TERMINATED)
.await
.map_err(KernelError::ProcessExit)
.unwrap();
let process_info = process.info().map_err(RunTestError::ProcessInfo)?;
match process_info.return_code {
TR_OK => {
Ok(ftest::Result_ { status: Some(ftest::Status::Passed), ..ftest::Result_::EMPTY })
}
TR_FAILED => {
// Add a preceding newline so that this does not mix with test output, as
// test output might not contain a newline at end.
test_logger.write_str("\ntest failed.\n").await?;
Ok(ftest::Result_ { status: Some(ftest::Status::Failed), ..ftest::Result_::EMPTY })
}
other => Err(RunTestError::UnexpectedReturnCode(other)),
}
}
pub fn validate_args(args: &Vec<String>) -> Result<(), ArgumentError> {
let restricted_flags = args
.iter()
.filter(|arg| {
return RESTRICTED_FLAGS.contains(arg.as_str());
})
.map(|s| s.clone())
.collect::<Vec<_>>()
.join(", ");
if restricted_flags.len() > 0 {
return Err(ArgumentError::RestrictedArg(restricted_flags));
}
Ok(())
}
}
/// Filter for use in `get_tests`.
enum TestFilter {
/// List _all_ tests in the test binary.
AllTests,
/// List only the disabled tests in the test binary.
DisabledTests,
}
/// Launches the Rust test binary specified by the given `Component` to retrieve a list of test
/// names.
async fn get_tests(
test_component: Arc<Component>,
filter: TestFilter,
) -> Result<Vec<String>, EnumerationError> {
let mut args = vec![
// Allow the usage of unstable options
"-Z".to_owned(),
"unstable-options".to_owned(),
// List installed commands
"--list".to_owned(),
];
if let TestFilter::DisabledTests = filter {
args.push("--ignored".to_owned());
}
// Load bearing to hold job guard.
let (process, _job, stdlogger) =
launch_component_process::<EnumerationError>(&test_component, args, None).await?;
// collect stdout in background before waiting for process termination.
let std_reader = LogStreamReader::new(stdlogger);
fasync::OnSignals::new(&process, zx::Signals::PROCESS_TERMINATED)
.await
.map_err(KernelError::ProcessExit)
.unwrap();
let logs = std_reader.get_logs().await?;
// TODO(fxbug.dev/4610): logs might not be utf8, fix the code.
let output = from_utf8(&logs)?;
let process_info = process.info().map_err(KernelError::ProcessInfo).unwrap();
if process_info.return_code != 0 {
// TODO(fxbug.dev/45858): Add a error logger to API so that we can display test stdout logs.
error!("Failed getting list of tests:\n{}", output);
return Err(EnumerationError::ListTest);
}
let mut tests = vec![];
let regex = Regex::new(r"^(.*): test$").unwrap();
for test in output.split("\n") {
if let Some(capture) = regex.captures(test) {
if let Some(name) = capture.get(1) {
tests.push(name.as_str().into());
}
}
}
Ok(tests)
}
/// Convenience wrapper around [`launch::launch_process`].
async fn launch_component_process<E>(
component: &Component,
args: Vec<String>,
test_invoke: Option<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: test_invoke.map(|test_invoke| vec![test_invoke]),
handle_infos: None,
})
.await?)
}
// TODO(fxbug.dev/45854): Add integration tests.
#[cfg(test)]
mod tests {
use {
super::*,
anyhow::{Context as _, Error},
fidl::endpoints::{ClientEnd, Proxy},
fidl_fuchsia_component_runner as fcrunner,
fidl_fuchsia_io::OPEN_RIGHT_READABLE,
fidl_fuchsia_test::{
Result_ as TestResult, RunListenerMarker, RunOptions, Status, SuiteMarker,
},
fuchsia_runtime::job_default,
itertools::Itertools,
matches::assert_matches,
pretty_assertions::assert_eq,
runner::component::ComponentNamespace,
runner::component::ComponentNamespaceError,
std::convert::TryFrom,
test_runners_lib::cases::TestCaseInfo,
test_runners_test_lib::{
assert_event_ord, collect_listener_event, names_to_invocation, ListenerEvent,
},
};
#[test]
fn validate_args_test() {
let restricted_flags = vec!["--nocapture", "--list"];
for flag in restricted_flags {
let args = vec![flag.to_string()];
let err = TestServer::validate_args(&args)
.expect_err(&format!("should error out for flag: {}", flag));
match err {
ArgumentError::RestrictedArg(f) => assert_eq!(f, flag),
}
}
let allowed_flags = vec!["--bench", "--anyflag", "--test", "--mycustomflag"];
for flag in allowed_flags {
let args = vec![flag.to_string()];
TestServer::validate_args(&args)
.expect(&format!("should not error out for flag: {}", flag));
}
}
fn create_ns_from_current_ns(
dir_paths: Vec<(&str, u32)>,
) -> Result<ComponentNamespace, ComponentNamespaceError> {
let mut ns = vec![];
for (path, permission) in dir_paths {
let chan = io_util::open_directory_in_namespace(path, permission)
.unwrap()
.into_channel()
.unwrap()
.into_zx_channel();
let handle = ClientEnd::new(chan);
ns.push(fcrunner::ComponentNamespaceEntry {
path: Some(path.to_string()),
directory: Some(handle),
..fcrunner::ComponentNamespaceEntry::EMPTY
});
}
ComponentNamespace::try_from(ns)
}
macro_rules! current_job {
() => {
job_default().duplicate(zx::Rights::SAME_RIGHTS)?
};
}
fn sample_test_component() -> Result<Arc<Component>, Error> {
let ns = create_ns_from_current_ns(vec![("/pkg", OPEN_RIGHT_READABLE)])?;
Ok(Arc::new(Component {
url: "fuchsia-pkg://fuchsia.com/rust-test-runner-test#meta/sample-rust-tests.cm"
.to_owned(),
name: "bin/sample_rust_tests".to_owned(),
binary: "bin/sample_rust_tests".to_owned(),
args: vec!["--my_custom_arg".to_string()],
ns: ns,
job: current_job!(),
}))
}
#[fuchsia_async::run_singlethreaded(test)]
async fn enumerate_simple_test() -> Result<(), Error> {
let component = sample_test_component().unwrap();
let server = TestServer::new();
let expected: Vec<TestCaseInfo> = vec![
TestCaseInfo { name: "my_tests::sample_test_one".to_string(), enabled: true },
TestCaseInfo { name: "my_tests::ignored_failing_test".to_string(), enabled: false },
TestCaseInfo { name: "my_tests::ignored_passing_test".to_string(), enabled: false },
TestCaseInfo { name: "my_tests::passing_test".to_string(), enabled: true },
TestCaseInfo { name: "my_tests::failing_test".to_string(), enabled: true },
TestCaseInfo { name: "my_tests::sample_test_two".to_string(), enabled: true },
TestCaseInfo { name: "my_tests::test_custom_arguments".to_string(), enabled: true },
]
.into_iter()
.sorted()
.collect();
let actual: Vec<TestCaseInfo> = server
.enumerate_tests(component.clone())
.await?
.iter()
.sorted()
.map(Clone::clone)
.collect();
assert_eq!(&expected, &actual);
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn enumerate_empty_test_file() -> Result<(), Error> {
let ns = create_ns_from_current_ns(vec![("/pkg", OPEN_RIGHT_READABLE)])?;
let component = Arc::new(Component {
url: "fuchsia-pkg://fuchsia.com/rust-test-runner-test#meta/no-rust-tests.cm".to_owned(),
name: "bin/no_rust_tests".to_owned(),
binary: "bin/no_rust_tests".to_owned(),
args: vec![],
ns: ns,
job: current_job!(),
});
let server = TestServer::new();
assert_eq!(*server.enumerate_tests(component.clone()).await?, Vec::<TestCaseInfo>::new());
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn enumerate_huge_test() -> Result<(), Error> {
let ns = create_ns_from_current_ns(vec![("/pkg", OPEN_RIGHT_READABLE)])?;
let component = Arc::new(Component {
url: "fuchsia-pkg://fuchsia.com/rust-test-runner-test#meta/huge-rust-tests.cm"
.to_owned(),
name: "bin/huge_rust_tests".to_owned(),
binary: "bin/huge_rust_tests".to_owned(),
args: vec![],
ns: ns,
job: current_job!(),
});
let server = TestServer::new();
let actual_tests: Vec<TestCaseInfo> = server
.enumerate_tests(component.clone())
.await?
.iter()
.sorted()
.map(Clone::clone)
.collect();
let expected: Vec<TestCaseInfo> = (1..=1000)
.map(|i| TestCaseInfo { name: format!("test_{}", i), enabled: true })
.sorted()
.collect();
assert_eq!(&expected, &actual_tests);
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn enumerate_invalid_file() -> Result<(), Error> {
let ns = create_ns_from_current_ns(vec![("/pkg", OPEN_RIGHT_READABLE)])?;
let component = Arc::new(Component {
url: "fuchsia-pkg://fuchsia.com/rust-test-runner-test#meta/invalid-test.cm".to_owned(),
name: "bin/invalid".to_owned(),
binary: "bin/invalid".to_owned(),
args: vec![],
ns: ns,
job: current_job!(),
});
let server = TestServer::new();
let err = server
.enumerate_tests(component.clone())
.await
.expect_err("this function have error-ed out due to non-existent file.");
assert_matches!(err, EnumerationError::LaunchTest(..));
let is_valid_error = match &err {
EnumerationError::LaunchTest(arc) => match **arc {
launch::LaunchError::LoadInfo(
runner::component::LaunchError::LoadingExecutable(_),
) => true,
_ => false,
},
_ => false,
};
assert!(is_valid_error, "Invalid error: {:?}", err);
Ok(())
}
async fn run_tests(
invocations: Vec<Invocation>,
run_options: RunOptions,
) -> Result<Vec<ListenerEvent>, anyhow::Error> {
let component = sample_test_component().context("Cannot create test component")?;
let weak_component = Arc::downgrade(&component);
let server = TestServer::new();
let (run_listener_client, run_listener) =
fidl::endpoints::create_request_stream::<RunListenerMarker>()
.context("Failed to create run_listener")?;
let (test_suite_client, test_suite) =
fidl::endpoints::create_request_stream::<SuiteMarker>()
.context("failed to create suite")?;
let suite_proxy =
test_suite_client.into_proxy().context("can't convert suite into proxy")?;
fasync::Task::spawn(async move {
server
.serve_test_suite(test_suite, weak_component)
.await
.expect("Failed to run test suite")
})
.detach();
suite_proxy
.run(&mut invocations.into_iter().map(|i| i.into()), run_options, run_listener_client)
.context("cannot call run")?;
collect_listener_event(run_listener).await.context("Failed to collect results")
}
#[fuchsia_async::run_singlethreaded(test)]
async fn run_one_test() -> Result<(), Error> {
let events =
run_tests(names_to_invocation(vec!["my_tests::passing_test"]), RunOptions::EMPTY)
.await
.unwrap();
let expected_events = vec![
ListenerEvent::start_test("my_tests::passing_test"),
ListenerEvent::finish_test(
"my_tests::passing_test",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::finish_all_test(),
];
assert_eq!(expected_events, events);
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn run_multiple_tests_exclude_disabled_tests() -> Result<(), Error> {
let events = run_tests(
names_to_invocation(vec![
"my_tests::sample_test_one",
"my_tests::passing_test",
"my_tests::failing_test",
"my_tests::sample_test_two",
"my_tests::ignored_passing_test",
"my_tests::ignored_failing_test",
"my_tests::test_custom_arguments",
]),
RunOptions {
include_disabled_tests: Some(false),
parallel: Some(1),
arguments: Some(vec!["--my_custom_arg2".to_owned()]),
..RunOptions::EMPTY
},
)
.await
.unwrap();
let expected_events = vec![
ListenerEvent::start_test("my_tests::sample_test_one"),
ListenerEvent::finish_test(
"my_tests::sample_test_one",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::passing_test"),
ListenerEvent::finish_test(
"my_tests::passing_test",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::failing_test"),
ListenerEvent::finish_test(
"my_tests::failing_test",
TestResult { status: Some(Status::Failed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::sample_test_two"),
ListenerEvent::finish_test(
"my_tests::sample_test_two",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::ignored_passing_test"),
ListenerEvent::finish_test(
"my_tests::ignored_passing_test",
TestResult { status: Some(Status::Skipped), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::ignored_failing_test"),
ListenerEvent::finish_test(
"my_tests::ignored_failing_test",
TestResult { status: Some(Status::Skipped), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::test_custom_arguments"),
ListenerEvent::finish_test(
"my_tests::test_custom_arguments",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::finish_all_test(),
];
assert_eq!(expected_events, events);
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn run_multiple_tests_parallel() -> Result<(), Error> {
let mut events = run_tests(
names_to_invocation(vec![
"my_tests::sample_test_one",
"my_tests::passing_test",
"my_tests::failing_test",
"my_tests::sample_test_two",
"my_tests::ignored_passing_test",
"my_tests::ignored_failing_test",
]),
RunOptions {
include_disabled_tests: Some(false),
parallel: Some(4),
arguments: None,
..RunOptions::EMPTY
},
)
.await
.unwrap();
let mut expected_events = vec![
ListenerEvent::start_test("my_tests::sample_test_one"),
ListenerEvent::finish_test(
"my_tests::sample_test_one",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::passing_test"),
ListenerEvent::finish_test(
"my_tests::passing_test",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::failing_test"),
ListenerEvent::finish_test(
"my_tests::failing_test",
TestResult { status: Some(Status::Failed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::sample_test_two"),
ListenerEvent::finish_test(
"my_tests::sample_test_two",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::ignored_passing_test"),
ListenerEvent::finish_test(
"my_tests::ignored_passing_test",
TestResult { status: Some(Status::Skipped), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::ignored_failing_test"),
ListenerEvent::finish_test(
"my_tests::ignored_failing_test",
TestResult { status: Some(Status::Skipped), ..TestResult::EMPTY },
),
ListenerEvent::finish_all_test(),
];
assert_event_ord(&events);
expected_events.sort();
events.sort();
assert_eq!(expected_events, events);
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn run_multiple_tests_include_disabled_tests() -> Result<(), Error> {
let events = run_tests(
names_to_invocation(vec![
"my_tests::sample_test_two",
"my_tests::ignored_passing_test",
"my_tests::ignored_failing_test",
]),
RunOptions {
include_disabled_tests: Some(true),
parallel: Some(1),
arguments: None,
..RunOptions::EMPTY
},
)
.await
.unwrap();
let expected_events = vec![
ListenerEvent::start_test("my_tests::sample_test_two"),
ListenerEvent::finish_test(
"my_tests::sample_test_two",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::ignored_passing_test"),
ListenerEvent::finish_test(
"my_tests::ignored_passing_test",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("my_tests::ignored_failing_test"),
ListenerEvent::finish_test(
"my_tests::ignored_failing_test",
TestResult { status: Some(Status::Failed), ..TestResult::EMPTY },
),
ListenerEvent::finish_all_test(),
];
assert_eq!(expected_events, events);
Ok(())
}
}