aion-server 0.27.1

Aion workflow server library: HTTP, gRPC, WebSocket, and worker endpoints. Run it with the `aion` binary from the aion-cli crate.
Documentation
//! Red-first specimens for stage reporting: the sequence that separates
//! progress from a wedge, and the reporter's safety in every seat it is used
//! from.

use super::{STAGE_WAL_RECOVERY, StageReporter};
use crate::control::claim_at_birth;
use crate::control::pid_file::read;
use crate::control::test_records::{TestResult, intended, own_birth_record};

/// A stage write lands the token, the detail, and — the load-bearing part —
/// bumps the sequence and stamps the time on EVERY report, including reports
/// that leave the token unchanged.
///
/// A reader watching a store chew through 64 shards sees one token for
/// minutes. Without the sequence there is no way to tell that from a boot
/// that stopped at shard 17, and those two want opposite decisions.
#[test]
fn every_stage_write_advances_the_sequence() -> TestResult {
    let home = tempfile::tempdir()?;
    let guard = claim_at_birth(home.path(), &own_birth_record()?, intended()?)?;
    let reporter = guard.stage_reporter();

    reporter.report(STAGE_WAL_RECOVERY, "materializing shard 1 of 3".to_owned());
    let first = read(home.path())?.ok_or("the stage write must land")?;
    assert_eq!(first.stage.as_deref(), Some(STAGE_WAL_RECOVERY));
    assert_eq!(
        first.stage_detail.as_deref(),
        Some("materializing shard 1 of 3")
    );
    assert_eq!(first.stage_seq, 1);
    assert!(
        first.stage_updated_at_unix_secs > 0,
        "a stage write must stamp when it happened"
    );

    reporter.report(STAGE_WAL_RECOVERY, "materializing shard 2 of 3".to_owned());
    let second = read(home.path())?.ok_or("the second stage write must land")?;
    assert_eq!(
        second.stage.as_deref(),
        first.stage.as_deref(),
        "the specimen exercises an UNCHANGED token on purpose"
    );
    assert_eq!(
        second.stage_seq, 2,
        "the sequence must advance even when the token does not — that is the \
         whole discriminator between progressing and stuck"
    );
    Ok(())
}

/// The rendered stage line is the one sentence every surface shows, so it is
/// pinned here rather than re-derived by each of them.
#[test]
fn the_stage_line_joins_the_token_and_the_detail() -> TestResult {
    let home = tempfile::tempdir()?;
    let guard = claim_at_birth(home.path(), &own_birth_record()?, intended()?)?;
    guard.stage_reporter().report(
        STAGE_WAL_RECOVERY,
        "materializing shard 17 of 64".to_owned(),
    );
    let record = read(home.path())?.ok_or("the stage write must land")?;
    assert_eq!(
        record.stage_line().as_deref(),
        Some("wal-recovery — materializing shard 17 of 64")
    );
    assert!(
        record.stage_age_secs().is_some(),
        "a written stage has a measurable age"
    );
    Ok(())
}

/// The reporter is handed into blocking closures on other threads (the store
/// build reports from the blocking pool), so it must be `Send` and cloneable
/// and its clones must write the SAME record. Proven by driving it from a
/// spawned thread and reading the result back through the guard.
#[test]
fn a_cloned_reporter_writes_the_same_record_from_another_thread() -> TestResult {
    let home = tempfile::tempdir()?;
    let guard = claim_at_birth(home.path(), &own_birth_record()?, intended()?)?;
    let reporter = guard.stage_reporter();
    let worker = std::thread::spawn(move || {
        reporter.report(STAGE_WAL_RECOVERY, "materializing shard 9 of 9".to_owned());
    });
    worker.join().map_err(|_panicked| "stage thread panicked")?;

    let on_disk = read(home.path())?.ok_or("the cross-thread stage write must land")?;
    assert_eq!(
        on_disk.stage_detail.as_deref(),
        Some("materializing shard 9 of 9")
    );
    assert_eq!(
        guard.record()?,
        on_disk,
        "the guard's own copy must follow a stage written from another thread, \
         so its exit compare and every reader see one truth"
    );
    Ok(())
}

/// A detached reporter — the embedder/test seat — writes nowhere and refuses
/// nothing. It exists so the boot path has one uniform way to narrate rather
/// than an `Option` every seam must handle.
#[test]
fn a_detached_reporter_is_a_silent_no_op() -> TestResult {
    let home = tempfile::tempdir()?;
    StageReporter::detached().report(STAGE_WAL_RECOVERY, "nowhere to go".to_owned());
    assert_eq!(
        read(home.path())?,
        None,
        "a detached reporter must not mint a record"
    );
    Ok(())
}