| // 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::{Context as _, Error}, |
| fuchsia_async as fasync, |
| fuchsia_syslog::{fx_log_err, fx_log_info}, |
| futures::future::BoxFuture, |
| futures::{future, select, Future, FutureExt}, |
| pin_utils::pin_mut, |
| }; |
| |
| #[macro_use] |
| pub mod expect; |
| |
| // Test harnesses |
| pub mod access; |
| pub mod bootstrap; |
| pub mod control; |
| pub mod emulator; |
| pub mod host_driver; |
| pub mod host_watcher; |
| pub mod inspect; |
| pub mod low_energy_central; |
| pub mod low_energy_peripheral; |
| pub mod profile; |
| |
| /// A `TestHarness` is a type that provides an interface to test cases for interacting with |
| /// functionality under test. For example, a WidgetHarness might provide controls for interacting |
| /// with and meausuring a Widget, allowing us to easily write tests for Widget functionality. |
| /// |
| /// A `TestHarness` defines how to initialize (via `init()`) the harness resources and how to |
| /// terminate (via `terminate()`) them when done. The `init()` function can also provide some |
| /// environment resources (`env`) to be held for the test duration, and also a task (`runner`) that |
| /// can be executed to perform asynchronous behavior necessary for the test harness to function |
| /// correctly. |
| pub trait TestHarness: Sized { |
| /// The type of environment needed to be held whilst the test runs. This is normally used to |
| /// keep resources open during the test, and allow a graceful shutdown in `terminate`. |
| type Env: Send + 'static; |
| |
| /// A future that models any background computation the harness needs to process. If no |
| /// processing is needed, implementations should use `future::Pending` to model a future that |
| /// never returns Poll::Ready |
| type Runner: Future<Output = Result<(), Error>> + Unpin + Send + 'static; |
| |
| fn init() -> BoxFuture<'static, Result<(Self, Self::Env, Self::Runner), Error>>; |
| fn terminate(env: Self::Env) -> BoxFuture<'static, Result<(), Error>>; |
| } |
| |
| /// We can run any test which is an async function from some harness `H` to a result |
| async fn run_with_harness<H, F, Fut>(test_func: F) -> Result<(), Error> |
| where |
| H: TestHarness, |
| F: FnOnce(H) -> Fut + Send + 'static, |
| Fut: Future<Output = Result<(), Error>> + Send + 'static, |
| { |
| let (harness, env, runner) = H::init().await.context("Error initializing harness")?; |
| |
| let run_test = test_func(harness); |
| pin_mut!(run_test); |
| pin_mut!(runner); |
| |
| let result = select! { |
| test_result = run_test.fuse() => test_result, |
| runner_result = runner.fuse() => runner_result.context("Error running harness background task"), |
| }; |
| |
| let term_result = H::terminate(env).await.context("Error terminating harness"); |
| // Return test failure if it exists, else return terminate failure, else return the |
| // successful test result |
| result.and_then(|ok| term_result.map(|_| ok)) |
| } |
| |
| /// Sets up the test environment and the given test case. Each integration test case is an |
| /// asynchronous function from some harness `H` to the result of the test run. |
| pub fn run_test<H, Fut>( |
| test: impl FnOnce(H) -> Fut + Send + 'static, |
| name: &str, |
| ) -> Result<(), Error> |
| where |
| Fut: Future<Output = Result<(), Error>> + Send + 'static, |
| H: TestHarness, |
| { |
| let mut executor = fasync::Executor::new().context("error creating event loop")?; |
| fx_log_info!("[ RUN ] {}...", name); |
| let result = executor.run_singlethreaded(run_with_harness(test)); |
| if let Err(err) = &result { |
| fx_log_err!("[ \x1b[31mFAILED\x1b[0m ] {}: Error running test: {}", name, err); |
| } else { |
| fx_log_info!("[ \x1b[32mPASSED\x1b[0m ] {}", name); |
| } |
| result |
| } |
| |
| /// The Unit type can be used as the empty test-harness - it does no initialization and no |
| /// termination. This is largely useful when using the `run_suite!` macro, which takes a sequence |
| /// of tests to run with harnesses - if there are tests that don't actually need a harness, then a |
| /// unit parameter can be passed. |
| impl TestHarness for () { |
| type Env = (); |
| type Runner = future::Pending<Result<(), Error>>; |
| |
| fn init() -> BoxFuture<'static, Result<(Self, Self::Env, Self::Runner), Error>> { |
| async { Ok(((), (), future::pending())) }.boxed() |
| } |
| fn terminate(_env: Self::Env) -> BoxFuture<'static, Result<(), Error>> { |
| future::ok(()).boxed() |
| } |
| } |
| |
| /// If A and B are test harnesses, then so is the pair (A,B). This is recursive, so (A, (B, C)) is |
| /// also a valid harness (where C is a harness). Any heterogenous list of Harness types is a valid |
| /// harness. |
| impl<A, B> TestHarness for (A, B) |
| where |
| A: TestHarness + Send, |
| B: TestHarness + Send, |
| { |
| type Env = (A::Env, B::Env); |
| type Runner = BoxFuture<'static, Result<(), Error>>; |
| |
| fn init() -> BoxFuture<'static, Result<(Self, Self::Env, Self::Runner), Error>> { |
| async { |
| let a = A::init().await?; |
| let b = B::init().await?; |
| // We want to return the first error immediately, if there is one, otherwise |
| // continue until the second future returns either successfully or an error |
| let fut = future::try_select(a.2, b.2) |
| .then(|res| match res { |
| // One of the futures returned successfully; continue processing the |
| // other |
| Ok(future::Either::Left((_, b))) => b.boxed(), |
| Ok(future::Either::Right((_, a))) => a.boxed(), |
| // We've received an error; return it |
| Err(e) => future::ready(Err(e.factor_first().0)).boxed(), |
| }) |
| .boxed(); |
| Ok(((a.0, b.0), (a.1, b.1), fut)) |
| } |
| .boxed() |
| } |
| fn terminate(_env: Self::Env) -> BoxFuture<'static, Result<(), Error>> { |
| let (env_a, env_b) = _env; |
| async { |
| let a = A::terminate(env_a).await; |
| let b = B::terminate(env_b).await; |
| a.and(b) |
| } |
| .boxed() |
| } |
| } |
| |
| // Prints out the test name and runs the test. |
| macro_rules! run_test { |
| ($name:ident) => {{ |
| crate::harness::run_test($name, stringify!($name)) |
| }}; |
| } |
| |
| macro_rules! run_suite { |
| ($name:tt, [$($test:ident),+]) => {{ |
| fuchsia_syslog::fx_log_info!(">>> Running {} tests:", $name); |
| { |
| use fuchsia_bluetooth::util::CollectExt; |
| vec![$( run_test!($test), )*].into_iter().collect_results()?; |
| } |
| Ok(()) |
| }} |
| } |