mae 0.3.12

Opinionated async Rust framework for building Mae-Technologies micro-services — app scaffolding, repo layer, middleware, and test utilities.
Documentation
//! Container singletons for mae integration tests.
//!
//! Each sub-module owns one Docker container type and exposes:
//! - A static singleton (started lazily, guarded by `MAE_TESTCONTAINERS=1`).
//! - A per-test **isolation scope** (schema, keyspace, vhost, database) that
//!   prevents cross-test interference.
//! - A [`teardown`](MaeContainer::teardown) function that stops the container
//!   and clears the singleton so the next test-run starts fresh.
//!
//! All four container types implement [`MaeContainer`].  Call
//! [`teardown_all()`] from your global test teardown hook to stop every
//! container in one shot.
//!
//! # Stability contract for issue #40
//! The trait and module layout are intentionally fixed here.  Issue #40 should
//! fill in the `start()` implementations for Redis, Neo4j, and RabbitMQ while
//! keeping the public API below unchanged.

pub mod neo4j;
pub mod postgres;
pub mod rabbitmq;
pub mod redis;

use std::future::Future;
use std::sync::atomic::{AtomicUsize, Ordering};

/// Number of concurrent `#[mae_test(docker, teardown = …)]` cases sharing containers.
static DOCKER_TEST_GUARDS: AtomicUsize = AtomicUsize::new(0);

/// Mark the start of a docker-gated test that will call [`teardown_all`] on exit.
///
/// Generated by `#[mae_test(docker, teardown = …)]`.  Prevents parallel tests from
/// tearing down shared containers while siblings are still running.
pub fn docker_test_guard_enter() {
    DOCKER_TEST_GUARDS.fetch_add(1, Ordering::SeqCst);
}

/// Common interface shared by every Mae container singleton.
///
/// Implementors are zero-sized unit types (`struct PostgresContainer;` etc.)
/// that act as namespaces — all state lives in module-level statics.
pub trait MaeContainer {
    /// Isolation scope created per test (schema, keyspace, vhost, …).
    type Scope: Send + 'static;

    /// Start (or return the already-running) container singleton.
    ///
    /// Returns `Some(())` when the container is running, `None` when
    /// `MAE_TESTCONTAINERS` is not set to `1`/`true`.
    fn start() -> impl Future<Output = Option<()>> + Send;

    /// Create a fresh isolation scope for one test.
    ///
    /// Callers must drop / clean up the scope when the test finishes.
    fn scope() -> impl Future<Output = anyhow::Result<Self::Scope>> + Send;

    /// Stop the container and reset the singleton.
    ///
    /// Safe to call even when the container was never started.
    fn teardown() -> impl Future<Output = ()> + Send;
}

/// Stop **all** Mae container singletons when the last guarded docker test exits.
///
/// Pair with [`docker_test_guard_enter`] (done automatically by
/// `#[mae_test(docker, teardown = …)]`).  Parallel tests can share containers
/// safely; the last test to finish stops them.
///
/// For unconditional shutdown (e.g. process exit), use [`teardown_all_force`].
pub async fn teardown_all() {
    let remaining = DOCKER_TEST_GUARDS.fetch_sub(1, Ordering::SeqCst);
    if remaining == 1 {
        teardown_all_force().await;
    } else if remaining == 0 {
        // `teardown_all` without a matching enter — restore counter, skip teardown.
        DOCKER_TEST_GUARDS.fetch_add(1, Ordering::SeqCst);
    }
}

/// Stop every container singleton immediately, regardless of guard count.
pub async fn teardown_all_force() {
    DOCKER_TEST_GUARDS.store(0, Ordering::SeqCst);
    postgres::PostgresContainer::teardown().await;
    redis::RedisContainer::teardown().await;
    neo4j::Neo4jContainer::teardown().await;
    rabbitmq::RabbitMqContainer::teardown().await;
}