e2e/
lib.rs

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    /// Configuration for the tester.
40    config: C,
41    /// List of test suites to run.
42    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    /// Creates a new test suite instance.
129    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/// Re-exports for procedural macros.
180#[doc(hidden)]
181pub mod __private_reexports {
182    pub use async_trait::async_trait;
183}