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