Skip to main content

vibe_tests/base/
engines.rs

1//! Engine accessor and lifecycle management.
2//! Provides global singleton engine with automatic init and cleanup.
3
4use crate::EngineTests;
5use ctor::ctor;
6use dtor::dtor;
7use std::sync::OnceLock;
8use tokio::sync::{Mutex, MutexGuard};
9
10/// Global engine singleton.
11/// Initialized by `engine_config!` via `#[ctor]`, cleaned up by `#[dtor]`.
12pub static ENGINE: OnceLock<Mutex<EngineTests>> = OnceLock::new();
13
14/// Returns the initialized engine instance.
15/// First call runs `init()` (compose up + on_start). Subsequent calls return immediately.
16pub async fn engine() -> MutexGuard<'static, EngineTests> {
17    let mutex = ENGINE.get().expect("engine_config! not called");
18    let mut engine = mutex.lock().await;
19    engine.init().await.expect("Failed to initialize engine");
20    engine
21}
22
23/// Configures and stores the engine before tests run.
24/// Usage: `vibe_tests::engine_config! { EngineTests::builder()...build().expect("...") }`
25#[macro_export]
26macro_rules! engine_config {
27    ($($tt:tt)*) => {
28        #[$crate::ctor::ctor(unsafe)]
29        #[allow(non_snake_case)]
30        fn init_engine_globally() {
31            $crate::base::engines::ENGINE.get_or_init(|| {
32                $crate::tokio::sync::Mutex::new({ $($tt)* })
33            });
34        }
35    };
36}
37
38/// Handle Ctrl+C gracefully — warn user about unreleased resources.
39#[ctor(unsafe)]
40fn setup_signal_handler() {
41    ctrlc::set_handler(move || {
42        println!("\n\n⚠ Ctrl+C detected — emergency exit.");
43        println!("Callback on_stop will not be called. Resources may not be released.");
44        std::process::exit(0);
45    })
46    .ok();
47}
48
49/// Cleanup on process exit.
50/// Calls engine.shutdown() → compose down, on_stop, kill children.
51#[dtor(unsafe)]
52fn cleanup() {
53    if let Some(mutex) = ENGINE.get() {
54        if let Ok(mut engine) = mutex.try_lock() {
55            engine.shutdown();
56        }
57    }
58}