Skip to main content

nornir_git/
selftest.rs

1//! # Functional-status emit bridge — `nornir-git` → test matrix
2//!
3//! The single internal call site this crate uses to push a *functional* row
4//! into the [`nornir_testmatrix`] matrix, so `nornir test --features testmatrix`
5//! records whether `nornir-git`'s real git ops (init / commit / head-sha /
6//! head-branch / worktree-freshness / url classification) ACTUALLY work.
7//!
8//! ## Release strips everything
9//! The `testmatrix` cargo feature gates the whole bridge: with it OFF (the
10//! release default) [`emit`] is an `#[inline]` no-op and `nornir-testmatrix` is
11//! not even linked. Lit ONLY under `cargo … --features testmatrix`.
12
13/// The aspect tag every self-emitted row carries (matches `functional_row`).
14pub const ASPECT_FUNCTIONAL: &str = "functional";
15
16/// Push ONE functional-status row for a single real check (feature ON).
17#[cfg(feature = "testmatrix")]
18#[inline]
19pub fn emit(component: &str, check: &str, ok: bool, detail: &str) {
20    nornir_testmatrix::functional_status(component, check, ok, detail);
21}
22
23/// No-op stub when the `testmatrix` feature is OFF (release builds).
24#[cfg(not(feature = "testmatrix"))]
25#[inline]
26pub fn emit(_component: &str, _check: &str, _ok: bool, _detail: &str) {}
27
28/// `assert!`-with-emit: assert on the real return value AND record the verdict
29/// as a matrix row.
30#[macro_export]
31macro_rules! assert_emit {
32    ($component:expr, $check:expr, $ok:expr, $($detail:tt)+) => {{
33        let __ok: bool = $ok;
34        let __detail = ::std::format!($($detail)+);
35        $crate::selftest::emit($component, $check, __ok, &__detail);
36        ::std::assert!(__ok, "{}::{} — {}", $component, $check, __detail);
37    }};
38}
39
40// ─── inject-and-assert wiring test (feature ON only) ─────────────────────────
41#[cfg(all(test, feature = "testmatrix"))]
42mod wiring {
43    use crate::gitio;
44    use std::io::Write;
45
46    /// Drive `nornir-git`'s real git ops on a synthetic repo, emit an honest row
47    /// per surface, then drain the process-global functional buffer and assert
48    /// the rows landed — proving the emit bridge is wired end to end.
49    #[test]
50    fn emits_honest_rows_for_git_ops() {
51        let dir = tempfile::tempdir().unwrap();
52        let root = dir.path();
53
54        // Surface: init a fresh repo.
55        let inited = gitio::init(root).is_ok();
56        crate::selftest::emit("nornir-git", "init_creates_repo", inited, &format!("root={}", root.display()));
57
58        // Seed a file + make the first commit.
59        {
60            let mut f = std::fs::File::create(root.join("README.md")).unwrap();
61            writeln!(f, "hello").unwrap();
62        }
63        let sha = gitio::commit_all(root, "initial");
64        let committed = sha.as_ref().map(|s| s.len() >= 40).unwrap_or(false);
65        crate::selftest::emit(
66            "nornir-git",
67            "commit_all_returns_sha",
68            committed,
69            &format!("sha_len={:?}", sha.as_ref().map(|s| s.len())),
70        );
71
72        // Surface: read HEAD sha back — must equal what commit_all reported.
73        let head = gitio::head_sha(root);
74        let head_ok = matches!((&head, &sha), (Ok(h), Ok(s)) if h == s);
75        assert_emit!(
76            "nornir-git",
77            "head_sha_matches_commit",
78            head_ok,
79            "head={:?} commit={:?}",
80            head.as_deref().ok(),
81            sha.as_deref().ok()
82        );
83
84        // Surface: read the HEAD branch name.
85        let branch = gitio::head_branch(root);
86        crate::selftest::emit(
87            "nornir-git",
88            "head_branch_resolves",
89            branch.as_ref().map(|b| !b.is_empty()).unwrap_or(false),
90            &format!("branch={:?}", branch.as_deref().ok()),
91        );
92
93        // Surface: worktree freshness reads without error on a clean repo.
94        let fresh = gitio::worktree_freshness(root);
95        crate::selftest::emit(
96            "nornir-git",
97            "worktree_freshness_reads",
98            fresh.is_ok(),
99            &format!("ok={}", fresh.is_ok()),
100        );
101
102        // Pure surfaces: url classification (no I/O).
103        let ssh_ok = gitio::is_ssh_url("git@codeberg.org:nordisk/nornir.git")
104            && !gitio::is_ssh_url("https://codeberg.org/nordisk/nornir.git");
105        assert_emit!("nornir-git", "is_ssh_url_classifies", ssh_ok, "ssh+https classified");
106        let rewrite_ok = gitio::https_to_ssh("https://codeberg.org/nordisk/nornir.git")
107            .as_deref()
108            .map(|s| gitio::is_ssh_url(s))
109            .unwrap_or(false);
110        assert_emit!("nornir-git", "https_to_ssh_roundtrips", rewrite_ok, "https→ssh yields ssh url");
111
112        let rows = nornir_testmatrix::drain_functional_rows();
113        for want in [
114            "init_creates_repo",
115            "commit_all_returns_sha",
116            "head_sha_matches_commit",
117            "head_branch_resolves",
118            "worktree_freshness_reads",
119            "is_ssh_url_classifies",
120            "https_to_ssh_roundtrips",
121        ] {
122            assert!(
123                rows.iter().any(|r| r.suite == "nornir-git" && r.test_name == want && r.status == "pass"),
124                "missing green row {want}: {rows:?}"
125            );
126        }
127        assert!(rows.iter().all(|r| r.aspect == "functional"), "rows={rows:?}");
128    }
129}