use std::panic::AssertUnwindSafe;
pub use self::{
reporter::{Reporter, console::ConsoleReporter},
traits::{Test, TestSuite, TestSuiteFactory},
};
pub use e2e_macro::test_suite;
use futures::FutureExt;
mod reporter;
mod traits;
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct TestResult {
pub name: String,
pub ignored: bool,
pub error: Option<TestError>,
}
impl TestResult {
pub fn new(name: String) -> Self {
Self {
name,
ignored: false,
error: None,
}
}
pub fn passed(&self) -> bool {
self.error.is_none()
}
pub fn set_ignored(&mut self, ignored: bool) {
self.ignored = ignored;
}
pub fn set_error(&mut self, error: TestError) {
self.error = Some(error);
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct TestSuiteResult {
pub name: String,
passed: bool,
pub tests: Vec<TestResult>,
pub error: Option<TestError>,
}
impl TestSuiteResult {
pub fn new(name: String) -> Self {
Self {
name,
passed: true,
tests: Vec::new(),
error: None,
}
}
pub fn add_test_result(&mut self, result: TestResult) {
if !result.passed() {
self.passed = false;
}
self.tests.push(result);
}
pub fn set_error(&mut self, error: TestError) {
self.error = Some(error);
self.passed = false;
}
}
pub fn init() {
std::panic::set_hook(Box::new(|_| {
}));
}
#[derive(Debug)]
pub struct Tester<C: std::fmt::Debug + 'static> {
config: C,
test_suites: Vec<Box<dyn TestSuiteFactory<C>>>,
reporter: Box<dyn Reporter>,
results: Vec<TestSuiteResult>,
}
impl<C: std::fmt::Debug + 'static> Tester<C> {
pub fn new(config: C) -> Self {
Self {
config,
test_suites: Vec::new(),
reporter: Box::new(ConsoleReporter::new()),
results: Vec::new(),
}
}
pub fn add_suite(&mut self, factory: Box<dyn TestSuiteFactory<C>>) {
self.test_suites.push(factory);
}
pub async fn run(mut self) -> anyhow::Result<()> {
for factory in &std::mem::take(&mut self.test_suites) {
let name = factory.name();
let mut result = TestSuiteResult::new(name.clone());
self.reporter.on_test_suite_creation_started(&name);
let suite_result = factory
.create_suite(&self.config)
.await
.map_err(TestError::CreateSuite);
self.reporter
.on_test_suite_creation_finished(&name, suite_result.as_ref().err());
self.reporter.on_test_suite_start(&name);
match suite_result {
Ok(suite) => {
self.run_suite(suite, &mut result).await;
}
Err(err) => {
result.set_error(err);
}
}
self.reporter.on_test_suite_end(&name, &result);
self.results.push(result);
}
Ok(())
}
async fn run_suite(&mut self, suite: Box<dyn TestSuite>, result: &mut TestSuiteResult) {
if let Err(err) = suite.before_all().await.map_err(TestError::BeforeAll) {
result.set_error(err);
return;
}
let has_only = suite.tests().iter().any(|test| test.only());
for test in suite.tests() {
let mut test_result = TestResult::new(test.name());
if test.ignore() || (has_only && !test.only()) {
test_result.set_ignored(true);
self.reporter.on_test_ignored(&test.name());
result.add_test_result(test_result);
continue;
}
if let Err(err) = suite.before_each().await.map_err(TestError::BeforeEach) {
test_result.set_error(err);
result.add_test_result(test_result);
continue;
}
self.reporter.on_test_start(&test.name());
let test_run_result = match AssertUnwindSafe(test.run()).catch_unwind().await {
Ok(Ok(())) => Ok(()),
Ok(Err(err)) => Err(TestError::Test(err)),
Err(err) => {
let err = if let Some(err) = err.downcast_ref::<String>() {
anyhow::anyhow!("Test panicked with message: {}", err)
} else if let Some(err) = err.downcast_ref::<&str>() {
anyhow::anyhow!("Test panicked with message: {}", err)
} else {
anyhow::anyhow!("Test panicked with an unknown error type")
};
Err(TestError::Test(err))
}
};
self.reporter
.on_test_end(&test.name(), test_run_result.as_ref().err());
if let Err(err) = test_run_result {
test_result.set_error(err);
}
if let Err(err) = suite.after_each().await.map_err(TestError::AfterEach) {
test_result.set_error(err);
}
result.add_test_result(test_result);
}
if let Err(err) = suite.after_all().await.map_err(TestError::AfterAll) {
result.set_error(err);
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum TestError {
#[error("Failed to create test suite: {0:?}")]
CreateSuite(anyhow::Error),
#[error("Failed to run 'before_all' for the test suite: {0:?}")]
BeforeAll(anyhow::Error),
#[error("Failed to run 'before_each' the test suite: {0:?}")]
BeforeEach(anyhow::Error),
#[error("Failed to run 'after_each' the test: {0:?}")]
AfterEach(anyhow::Error),
#[error("Failed to run 'after_all' the test: {0:?}")]
AfterAll(anyhow::Error),
#[error("Test failed: {0:?}")]
Test(anyhow::Error),
}
#[doc(hidden)]
pub mod __private_reexports {
pub use async_trait::async_trait;
}