Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar

xtask/tests/readme_claims.rs

//! ★ **README-2 (owner 2026-10-26): every feature the front page claims has a test.**
//!
//! The front page's claims (the bullet list under the engine sentence, the long comma list
//! split into its items, and the engine sentence itself) are mapped here, one row each, to a
//! real test in this tree that holds the claim. The test is red when:
//!
//! * the README claims something with no row here (a new claim needs a test first);
//! * a row's claim is no longer in the README (the table may not outlive the words);
//! * a row's test is not in its file as a test (`fn <name>(` with `#[test]` or
//!   `#[tokio::test]` just above it).
//!
//! README-1 (same day): "geo-replication" overstated it. A gunnar.rs instance's twin runs in
//! the same zone as its main (the Gunnar guest lays front, main and twin in ONE zone); a
//! self-hoster joins a twin anywhere that reaches the primary (`gunnar control twin join`).
//! The page now says "replication to a twin", and where the twin can be.
//!
//! Both `.nornir/README.md` and the generated root `README.md` are read: the root is the
//! source rendered verbatim under a two-line header.

use std::path::{Path, PathBuf};

/// `(the claim as the README words it, the file holding its test, the test)`.
const CLAIMS: &[(&str, &str, &str)] = &[
    (
        "two engine variants",
        "crates/gunnar-server/src/storage.rs",
        "the_engine_is_reported_as_name_description_and_source_repository",
    ),
    (
        "Built-in replication to a twin gunnar: self-hosted, the twin is any box that reaches the primary (`gunnar control twin join`); a gunnar.rs instance's twin runs in the same zone as its main",
        "crates/gunnar-cli/tests/twin_push_is_framed.rs",
        "a_pushed_pack_is_a_frame_before_its_refs_and_the_twin_and_a_restore_hold_it",
    ),
    (
        "Vetra hardware appliance variant: OS-less, hardened kernel",
        "crates/gunnar-cli/tests/feature_split.rs",
        "the_appliance_links_no_worktree_no_client_transport_and_no_subprocess",
    ),
    (
        "Encrypted repositories, quantum-safe by default: files are sealed on the client, the key ring is wrapped only to members' ML-KEM-768 + X25519 keys, and the server holds ciphertext only",
        "crates/gunnar-cli/tests/encrypted_repo.rs",
        "crypt_init_starts_quantum_safe_by_default",
    ),
    ("Free to host your own", "crates/gunnar/src/main.rs", "a_bare_gunnar_serves_on_loopback"),
    ("NVMe", "crates/gunnar-recipe/tests/cli_matrix.rs", "the_build_kernel_line_plans_the_donor_first"),
    ("io_uring", "crates/gunnar-server/src/storage/writer.rs", "select_is_decided_once_and_never_builds_uring_the_kernel_refuses"),
    ("XFS on raid0", "crates/gunnar-server/tests/slow_disk_placement.rs", "an_unsigned_config_is_read_and_reported_as_integrity_only"),
    ("OS-less appliance kernel", "crates/gunnar-recipe/tests/machine_named.rs", "the_shipped_kernel_already_has_both_symbols_the_machine_would_add"),
    (
        "WireGuard",
        "crates/gunnar-server/src/pairing.rs",
        "a_wireguard_box_with_no_fed_front_asks_the_door_again_before_raising_the_tunnel",
    ),
    ("Prometheus", "crates/gunnar-cli/tests/metrics_endpoint.rs", "a_client_scrapes_every_counter_with_its_kind"),
    ("Git LFS", "crates/gunnar-cli/tests/lfs_http.rs", "a_clone_over_http_materialises_its_lfs_content_and_a_push_puts_more_back"),
    ("smart HTTP", "crates/gunnar-cli/tests/http_push_is_attributable.rs", "a_push_names_its_credential_over_ssh_and_over_http"),
    ("webhooks", "crates/gunnar-server/tests/lean_egress.rs", "a_webhook_delivery_leaves_through_the_door"),
    (
        "replication to a twin",
        "crates/gunnar-cli/tests/twin_push_is_framed.rs",
        "a_pushed_pack_is_a_frame_before_its_refs_and_the_twin_and_a_restore_hold_it",
    ),
    ("sshsig/ed25519 signed pushes", "crates/gunnar-cli/tests/verify_chain.rs", "tampered_unknown_and_unverifiable_signatures_are_kept_apart"),
    ("transparency log", "crates/gunnar-cli/tests/transparency.rs", "a_push_proves_its_place_in_the_log_offline"),
];

/// Bullets that are no claim about the code, and why.
const NOT_CODE_CLAIMS: &[(&str, &str)] = &[(
    "The exact same code is hosted at https://www.gunnar.rs",
    "where it is hosted, not what the code does",
)];

fn root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .expect("the workspace root")
        .to_owned()
}

/// The README's claims: each bullet of the first list (the long comma list split into its
/// items), and "two engine variants" when the engine sentence says it.
fn claims(readme: &str) -> Vec<String> {
    let mut out = Vec::new();
    if readme.contains("two engine variants") {
        out.push("two engine variants".to_owned());
    }
    let mut in_list = false;
    for line in readme.lines() {
        if let Some(item) = line.strip_prefix("* ") {
            in_list = true;
            let item = item.trim().trim_end_matches('.');
            if item.matches(", ").count() >= 4 {
                out.extend(item.split(", ").map(|c| c.trim().to_owned()));
            } else {
                out.push(item.to_owned());
            }
        } else if in_list {
            break;
        }
    }
    out
}

/// Whether `file` holds `name` as a test: `fn name(` with a test attribute in the six lines
/// above it.
fn is_a_test(file: &str, name: &str) -> bool {
    let lines: Vec<&str> = file.lines().collect();
    lines.iter().enumerate().any(|(i, l)| {
        l.contains(&format!("fn {name}("))
            && lines[i.saturating_sub(6)..i]
                .iter()
                .any(|a| a.contains("#[test]") || a.contains("#[tokio::test"))
    })
}

#[test]
fn every_readme_claim_has_a_test_and_every_row_a_claim() {
    for page in [".nornir/README.md", "README.md"] {
        let readme = std::fs::read_to_string(root().join(page)).expect("the README");
        let said = claims(&readme);
        assert!(
            said.len() >= 15,
            "{page}: the claims were not found ({} of them): {said:?}",
            said.len()
        );
        let unmapped: Vec<&String> = said
            .iter()
            .filter(|c| {
                !CLAIMS.iter().any(|(claim, _, _)| claim == c)
                    && !NOT_CODE_CLAIMS.iter().any(|(claim, _)| claim == c)
            })
            .collect();
        assert!(
            unmapped.is_empty(),
            "{page} claims {unmapped:?} with no test in this table: add a real test, or say less"
        );
        let gone: Vec<&str> = CLAIMS
            .iter()
            .map(|(c, _, _)| *c)
            .filter(|c| !said.iter().any(|s| s == c))
            .collect();
        assert!(
            gone.is_empty(),
            "{page} no longer claims {gone:?}: drop the rows"
        );
    }
}

#[test]
fn every_rows_test_is_in_its_file() {
    let mut missing = Vec::new();
    for (claim, path, name) in CLAIMS {
        match std::fs::read_to_string(root().join(path)) {
            Ok(file) if is_a_test(&file, name) => {}
            Ok(_) => missing.push(format!("{claim}: {path} has no test `{name}`")),
            Err(e) => missing.push(format!("{claim}: {path}: {e}")),
        }
    }
    assert!(
        missing.is_empty(),
        "claims whose test is gone:\n  {}",
        missing.join("\n  ")
    );
}

#[test]
fn the_claim_reader_and_the_test_finder_read_what_they_say() {
    let page = "Two lines, with two engine variants here.\n\n* One thing.\n* a, b, c, d, e.\n\n## Next\n* not a claim\n";
    assert_eq!(
        claims(page),
        ["two engine variants", "One thing", "a", "b", "c", "d", "e"]
    );
    assert!(is_a_test("#[test]\nfn a_test() {}", "a_test"));
    assert!(is_a_test(
        "    #[tokio::test(flavor = \"multi_thread\")]\n    async fn b() {}",
        "b"
    ));
    assert!(
        !is_a_test("fn helper() {}", "helper"),
        "a helper is no test"
    );
}