Skip to main content

admixture_harness/
lib.rs

1//! Test harness for integration tests with admixture contexts.
2//!
3//! Provides the `#[admixture_test]` macro for writing integration tests
4//! with automatic context lifecycle management and lifecycle hooks.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use admixture::context;
10//! use admixture_harness::prelude::*;
11//!
12//! context! {
13//!     MyTestContext {
14//!         postgres: SqlxPostgresServiceSetup,
15//!     }
16//! }
17//!
18//! #[admixture_test(context = MyTestContext)]
19//! async fn test_database_operations(ctx: &MyTestContextRunning) -> Result<(), TestError> {
20//!     let client = ctx.postgres().client().await?;
21//!     // Test logic here
22//!     Ok(())
23//! }
24//! ```
25
26pub mod error;
27pub mod runner;
28
29use std::future::Future;
30use std::pin::Pin;
31
32// Re-export the macro
33pub use admixture_harness_macros::admixture_test;
34
35// Re-export runner components for external use
36pub use runner::{ContextGroupResult, TestFailure, TestResults, collect_tests, run_all_tests_impl, run_context_group};
37
38/// Generic context lifecycle manager trait.
39///
40/// Implemented by the macro for each context type to manage setup/teardown.
41/// Uses concrete types instead of type erasure for better type safety.
42#[allow(clippy::type_complexity)]
43pub trait ContextManager<C>: Send + Sync {
44    /// Start the context and return the running context.
45    fn start(
46        &self,
47    ) -> Pin<Box<dyn Future<Output = Result<C, Box<dyn std::error::Error + Send>>> + Send>>;
48
49    /// Stop the context.
50    fn stop(
51        &self,
52        ctx: C,
53    ) -> Pin<Box<dyn Future<Output = Result<(), Box<dyn std::error::Error + Send>>> + Send>>;
54}
55
56/// Type alias for hook functions with Higher-Rank Trait Bounds.
57///
58/// Hooks receive a reference to the running context of concrete type C
59/// and return a pinned future.
60pub use admixture::hooks::HookFn;
61
62/// Container for lifecycle hooks with concrete context type.
63///
64/// All hooks are optional. Hooks are executed at specific points in the test lifecycle:
65/// - `before_all`: After context starts, before any tests run
66/// - `after_all`: After all tests complete, before context stops (best-effort)
67/// - `before_each`: Before each individual test
68/// - `after_each`: After each individual test (always runs, even if test fails)
69pub use admixture::hooks::Hooks;
70
71/// Type alias for test functions with Higher-Rank Trait Bounds.
72///
73/// Test functions receive a reference to the running context of concrete type C
74/// and return a pinned future with the test result.
75pub type TestFn<C> = for<'a> fn(
76    &'a C,
77) -> Pin<Box<dyn Future<Output = Result<(), Box<dyn std::error::Error + Send>>> + Send + 'a>>;
78
79/// Type alias for group runner functions.
80///
81/// This is the type-erased boundary between test collection (inventory) and
82/// typed test execution. Each context type gets its own runner implementation.
83pub type GroupRunnerFn = fn(
84    &'static [&'static TestDescriptor],
85) -> Pin<Box<dyn Future<Output = ContextGroupResult> + Send>>;
86
87/// Descriptor for a registered integration test.
88///
89/// This struct is submitted to the `inventory` collection by the `#[admixture_test]` macro.
90#[derive(Copy, Clone)]
91pub struct TestDescriptor {
92    /// Name of the test function.
93    pub name: &'static str,
94
95    /// Module path where the test is defined.
96    pub module_path: &'static str,
97
98    /// Source file where the test is defined.
99    pub file: &'static str,
100
101    /// Line number where the test is defined.
102    pub line: u32,
103
104    /// Context type identifier (for grouping tests).
105    pub context_type: &'static str,
106
107    /// Type-erased entry point to run all tests for this context type.
108    ///
109    /// This function collects test functions from the per-context registry
110    /// and dispatches to the generic runner with concrete types.
111    pub run_group: GroupRunnerFn,
112}
113
114// Collect all registered integration tests.
115inventory::collect!(TestDescriptor);
116
117/// Prelude module for convenient imports.
118pub mod prelude {
119    pub use crate::admixture_test;
120    pub use crate::error::TestError;
121
122    /// Initialize tracing with indicatif support for pretty progress bars.
123    ///
124    /// Call this once at the beginning of your test suite to enable
125    /// visual progress bars and structured logging.
126    ///
127    /// It's safe to call this multiple times - it will only initialize once.
128    ///
129    /// # Example
130    ///
131    /// ```ignore
132    /// use admixture_harness::prelude::*;
133    ///
134    /// #[cfg(test)]
135    /// fn init_test_tracing() {
136    ///     init_tracing();
137    /// }
138    /// ```
139    pub fn init_tracing() {
140        use std::sync::Once;
141        static INIT: Once = Once::new();
142
143        INIT.call_once(|| {
144            // Just use a simple fmt subscriber for now
145            // Tests can override this with tracing-indicatif if they want
146            let _ = tracing_subscriber::fmt()
147                .with_target(false)
148                .with_test_writer()
149                .try_init();
150        });
151    }
152}