alkgit-substrate 0.1.0

The shipped alkgit backend composition: the reactive alkstore + alkblobs backend implementing alkgit's backend seam — reactive halves, cached read provider, engine twins, migrations, GC wiring, and the LFS surface (ADR-019/025)
Documentation
//! The test-harness helper home (review 006 R-3, the wave-10
//! consolidation): the one `#[cfg(test)]` module the suites' duplicated
//! helper copies collapsed into — the awaited-condition poll, the
//! record fixtures, the wake-runner spawn/join set, the unique-sqlite
//! db path and the scratch dir. Zero runtime coupling: the module is
//! compiled only under test, is `pub(crate)` (not public API
//! surface), and its consumers are the test modules across
//! `substrate/src/`. The engine-specific live-pg harness helpers
//! (schema + DSN plumbing) stay in [`crate::postgres::live_harness`];
//! this home is engine-neutral.
//!
//! The fixture-path helpers take the consuming module's name as the
//! `fixture` argument (the per-module temp prefixes the deleted copies
//! spelled inline), and keep the process id in the name so parallel
//! test binaries never collide; `unique_db_path` carries a process
//! counter (the sqlite twins open the same path twice in one test),
//! `scratch_dir` the nanosecond stamp.

#[cfg(feature = "sqlite")]
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;

use alkstore::StopToken;

use alkgit::{RepoRecord, Visibility};

use crate::reactor::Reactor;

/// Poll an awaited condition until it lands or the wall-clock deadline
/// expires (the suites' shared shape — the 10s deadline the
/// maintenance suite's copy pinned, the pass path identical at either
/// deadline). The predicate is sync; a test that must poll an async
/// read awaits it inside its own loop instead.
pub(crate) async fn wait_until(pred: impl Fn() -> bool) {
    let deadline = tokio::time::Instant::now() + Duration::from_secs(10);
    while !pred() && tokio::time::Instant::now() < deadline {
        tokio::time::sleep(Duration::from_millis(10)).await;
    }
    assert!(
        pred(),
        "the awaited condition did not land inside the deadline"
    );
}

/// The public record fixture.
pub(crate) fn record(id: &str) -> RepoRecord {
    RepoRecord {
        repo_id: id.to_owned(),
        visibility: Visibility::Public,
        grants: Default::default(),
    }
}

/// The private record fixture.
pub(crate) fn private_record(id: &str) -> RepoRecord {
    RepoRecord {
        repo_id: id.to_owned(),
        visibility: Visibility::Private,
        grants: Default::default(),
    }
}

/// Spawn the reactor's wake runner (the tests' shared shape) and
/// return its stop token and join handle — the join asserts the
/// runner ran clean.
pub(crate) fn spawn_wake_runner(
    reactor: std::sync::Arc<Reactor>,
) -> (StopToken, tokio::task::JoinHandle<()>) {
    let stop = StopToken::new();
    let join = tokio::spawn({
        let stop = stop.clone();
        async move {
            reactor
                .run_wake_runner(stop)
                .await
                .expect("the wake runner ran clean");
        }
    });
    (stop, join)
}

/// Cancel the stop token and join the spawned run inside the 5s
/// deadline, asserting a clean end (the wake-runner/stream-consumer
/// sets' shared shape).
pub(crate) async fn stop_and_join(stop: StopToken, join: tokio::task::JoinHandle<()>) {
    stop.cancel();
    tokio::time::timeout(Duration::from_secs(5), join)
        .await
        .expect("the run ended inside the deadline")
        .expect("the run ended clean");
}

/// A unique sqlite temp db path under the process id and a process
/// counter (the sqlite twins open the same path twice in one test) —
/// the engine-gated fixture the sqlite-combo suites consume.
#[cfg(all(test, feature = "sqlite"))]
pub(crate) fn unique_db_path(fixture: &str, tag: &str) -> std::path::PathBuf {
    static COUNTER: AtomicU64 = AtomicU64::new(0);
    let n = COUNTER.fetch_add(1, Ordering::Relaxed);
    std::env::temp_dir().join(format!(
        "alkgit-substrate-{fixture}-{}-{n}-{tag}.db",
        std::process::id()
    ))
}

/// A unique scratch directory under the process id and the
/// nanosecond stamp, created on demand (its contents the test's own
/// cleanup business).
pub(crate) fn scratch_dir(fixture: &str, tag: &str) -> std::path::PathBuf {
    let nanos = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_nanos();
    let dir = std::env::temp_dir().join(format!(
        "alkgit-substrate-{fixture}-{tag}-{}-{nanos}",
        std::process::id()
    ));
    std::fs::create_dir_all(&dir).unwrap_or_default();
    dir
}

/// The mem binding's record pair — the in-memory adapter bound onto
/// the core's registry traits with the write-through-provider shape
/// the twin assemblies construct (the removed public `mem_registry`
/// constructor's test-only survivor; review 006 R-4 — the production
/// composition of the same adapter is the entry's mem shape,
/// [`crate::compose::RegistryLegs::mem`]).
pub(crate) fn mem_registry_pair() -> (
    std::sync::Arc<dyn crate::registry::RegistryWrite>,
    std::sync::Arc<dyn alkgit::GitRegistryStore>,
) {
    use crate::registry::{CachedRegistryProvider, InMemoryRegistry, RegistryBackend};
    let registry = std::sync::Arc::new(InMemoryRegistry::new());
    let provider = std::sync::Arc::new(CachedRegistryProvider::new());
    let store = RegistryBackend::with_cached_provider(registry.clone(), provider);
    (registry, std::sync::Arc::new(store))
}