aion-server 0.31.0

Aion workflow server library: HTTP, gRPC, WebSocket, and worker endpoints. Run it with the `aion` binary from the aion-cli crate.
Documentation
//! A fixture-owned engine that is stopped when its binding ends.
//!
//! `Drop for Engine` deliberately leaves the beamr scheduler and the engine's
//! NIF seams alive: it closes the engine-task epoch and stops the timer wheel,
//! but stopping the scheduler and clearing the seams is `Engine::shutdown`'s
//! job and nothing else's. That is the engine's contract — shutdown is the
//! caller's act — so a fixture that builds an engine and merely drops it leaks
//! the scheduler's threads and, through the uncleared seams, every store clone
//! the seams reach (which is how a durable backend's writer lock outlives the
//! test that opened it).
//!
//! Holding the engine in this guard makes the fixture keep the engine's side of
//! that contract: the test stops the engine explicitly where it can, and the
//! guard covers every early return that `?` takes.
//!
//! This is a sibling of the lib target's own guard rather than a shared one:
//! an integration test cannot reach a `#[cfg(test)]` module inside the crate
//! it links against, so the two targets each carry their own.

use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

use aion::Engine;

/// What a fixture teardown failure is called in the harness output, so the
/// line is greppable whichever way it was reported, and whichever target
/// reported it: the lib target's own guard uses this same wording.
const TEARDOWN_FAILED: &str = "aion-server test fixture teardown failed";

/// An engine a fixture built, stopped when the fixture's binding ends.
pub struct EngineUnderTest {
    /// The engine this guard stops. Clone it for anything that takes a handle
    /// of its own; the guard keeps this one, so it still stops the engine.
    pub engine: Arc<Engine>,
    /// Whether the engine has already been stopped through this guard.
    stopped: AtomicBool,
}

impl EngineUnderTest {
    /// Take ownership of a fixture-built engine.
    pub fn new(engine: Arc<Engine>) -> Self {
        Self {
            engine,
            stopped: AtomicBool::new(false),
        }
    }

    /// Stop the engine and hand the outcome to the test. Idempotent: a second
    /// call (including the one `Drop` makes) is a no-op that reports success.
    ///
    /// # Errors
    ///
    /// Returns whatever `Engine::shutdown` returns on the first call.
    pub fn shutdown(&self) -> Result<(), aion::EngineError> {
        if self.stopped.swap(true, Ordering::SeqCst) {
            return Ok(());
        }
        self.engine.shutdown()
    }
}

impl Drop for EngineUnderTest {
    /// Report a teardown failure as a test failure, through an assertion, so
    /// the engine's own words reach the harness output.
    ///
    /// The one exception is a drop running while the thread is ALREADY
    /// unwinding: a panic raised during unwind aborts the process, which would
    /// erase the failure the test was in the middle of reporting. There the
    /// error goes to stderr beside that failure instead.
    fn drop(&mut self) {
        let failure = self
            .shutdown()
            .err()
            .map(|error| format!("{TEARDOWN_FAILED}: {error}"));
        if std::thread::panicking() {
            if let Some(message) = failure {
                eprintln!("{message}");
            }
            return;
        }
        assert!(
            failure.is_none(),
            "{}",
            failure.as_deref().unwrap_or_default()
        );
    }
}