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}