e2e/
lib.rs

1use std::fmt;
2
3pub use self::reporter::{Reporter, console::ConsoleReporter};
4/// Procedural macro for defining test suites.
5pub use e2e_macro::test_suite;
6
7mod reporter;
8
9#[derive(Debug)]
10pub struct Tester<C: std::fmt::Debug + 'static> {
11    /// Configuration for the tester.
12    config: C,
13    /// List of test suites to run.
14    test_suites: Vec<Box<dyn TestSuiteFactory<C>>>,
15    /// Reporter for test events.
16    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    /// Creates a new test suite instance.
109    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/// Re-exports for procedural macros.
163#[doc(hidden)]
164pub mod __private_reexports {
165    pub use async_trait::async_trait;
166}