1use std::fmt;
2
3pub use self::reporter::{Reporter, console::ConsoleReporter};
4pub use e2e_macro::test_suite;
6
7mod reporter;
8
9#[derive(Debug)]
10pub struct Tester<C: std::fmt::Debug + 'static> {
11 config: C,
13 test_suites: Vec<Box<dyn TestSuiteFactory<C>>>,
15 reporter: Box<dyn Reporter>,
17}
18
19impl<C: std::fmt::Debug + 'static> Tester<C> {
20 pub fn new(config: C) -> Self {
21 Self {
22 config,
23 test_suites: Vec::new(),
24 reporter: Box::new(ConsoleReporter::new()),
25 }
26 }
27
28 pub fn add_suite(&mut self, factory: Box<dyn TestSuiteFactory<C>>) {
29 self.test_suites.push(factory);
30 }
31
32 pub async fn run(self) -> Result<(), TestError> {
33 for factory in &self.test_suites {
34 let name = factory.name();
35 self.reporter.on_test_suite_creation_started(&name);
36 let suite = match factory
37 .create_suite(&self.config)
38 .await
39 .map_err(TestError::CreateSuite)
40 {
41 Ok(suite) => suite,
42 Err(err) => {
43 self.reporter.on_error(&err);
44 continue;
45 }
46 };
47 self.reporter.on_test_suite_creation_finished(&name);
48
49 self.reporter.on_test_suite_start(&name);
50 if let Err(err) = self.run_suite(suite).await {
51 self.reporter.on_error(&err);
52 }
53 self.reporter.on_test_suite_end(&name);
54 }
55
56 Ok(())
57 }
58
59 async fn run_suite(&self, suite: Box<dyn TestSuite>) -> Result<(), TestError> {
60 suite.before_all().await.map_err(TestError::BeforeAll)?;
61
62 for test in suite.tests() {
63 if test.ignore() {
64 self.reporter.on_test_ignored(&test.name());
65 continue;
66 }
67
68 suite.before_each().await.map_err(TestError::BeforeEach)?;
69 self.reporter.on_test_start(&test.name());
70 if let Err(err) = test
71 .run()
72 .await
73 .map_err(|err| TestError::Test(test.name(), err))
74 {
75 self.reporter.on_error(&err);
76 } else {
77 self.reporter.on_test_end(&test.name());
78 }
79 suite.after_each().await.map_err(TestError::AfterEach)?;
80 }
81
82 suite.after_all().await.map_err(TestError::AfterAll)?;
83
84 Ok(())
85 }
86}
87
88#[derive(Debug, thiserror::Error)]
89pub enum TestError {
90 #[error("Failed to create test suite: {0:?}")]
91 CreateSuite(anyhow::Error),
92 #[error("Failed to run 'before_all' for the test suite: {0:?}")]
93 BeforeAll(anyhow::Error),
94 #[error("Failed to run 'before_each' the test suite: {0:?}")]
95 BeforeEach(anyhow::Error),
96 #[error("Failed to run 'after_each' the test suite: {0:?}")]
97 AfterEach(anyhow::Error),
98 #[error("Failed to run 'after_all' the test suite: {0:?}")]
99 AfterAll(anyhow::Error),
100 #[error("Test {0} failed: {1:?}")]
101 Test(String, anyhow::Error),
102}
103
104#[async_trait::async_trait]
105pub trait TestSuiteFactory<C>: Send + Sync + 'static {
106 fn name(&self) -> String;
107
108 async fn create_suite(&self, config: &C) -> anyhow::Result<Box<dyn TestSuite>>;
110}
111
112impl<C: std::fmt::Debug + 'static> fmt::Debug for Box<dyn TestSuiteFactory<C>> {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 write!(f, "{}", self.name())
115 }
116}
117
118#[async_trait::async_trait]
119pub trait TestSuite: Send + Sync + 'static {
120 fn name(&self) -> String;
121
122 fn tests(&self) -> Vec<Box<dyn Test>>;
123
124 async fn before_all(&self) -> anyhow::Result<()> {
125 Ok(())
126 }
127
128 async fn before_each(&self) -> anyhow::Result<()> {
129 Ok(())
130 }
131
132 async fn after_each(&self) -> anyhow::Result<()> {
133 Ok(())
134 }
135
136 async fn after_all(&self) -> anyhow::Result<()> {
137 Ok(())
138 }
139}
140
141impl fmt::Debug for dyn TestSuite {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 write!(f, "{}", self.name())
144 }
145}
146
147#[async_trait::async_trait]
148pub trait Test: Send + Sync + 'static {
149 fn name(&self) -> String;
150 async fn run(&self) -> anyhow::Result<()>;
151 fn ignore(&self) -> bool {
152 false
153 }
154}
155
156impl fmt::Debug for dyn Test {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 write!(f, "{}", self.name())
159 }
160}
161
162#[doc(hidden)]
164pub mod __private_reexports {
165 pub use async_trait::async_trait;
166}