crates/gunnar-cli/tests/blame_search.rs
#![cfg(all(feature = "server", feature = "control"))]
//! **`blame` and `search`, end to end**, through stock git over real ssh.
//!
//! The `gunnar-server` unit tests prove the *algorithms* against a commit graph
//! whose oids the test chose. This proves the other half — the verb's store
//! resolution: a repository pushed by stock git, a revision resolved the way
//! `Refs.Log` resolves it, and the answer read back from the real store. The
//! oids blame is checked against are the ones `git rev-parse` reported at push
//! time, so this is the verb being checked against git, not against itself.
mod common;
use common::Harness;
use gunnar_cli::server::blame::blame;
use gunnar_cli::server::search::{search, Limits};
use gunnar_core::HashKind;
use gunnar_proto::RepoPath;
fn tools_present() -> bool {
["git", "ssh"].iter().all(|tool| {
std::process::Command::new(tool)
.arg("--version")
.output()
.is_ok()
})
}
/// Commit A writes three lines; commit B rewrites the middle one. Returns
/// `(oid_a, oid_b)` as `git rev-parse` reported them.
fn two_commits(harness: &Harness, work: &std::path::Path, url: &str) -> (String, String) {
harness
.git(
harness.dir.path(),
&["init", "--quiet", "-b", "main", work.to_str().unwrap()],
)
.expect_ok("git init");
std::fs::write(work.join("src.txt"), "line one\nline two\nline three\n").expect("write");
harness.git(work, &["add", "src.txt"]).expect_ok("add");
harness
.git(work, &["commit", "--quiet", "-m", "A"])
.expect_ok("commit A");
let a = harness.git(work, &["rev-parse", "HEAD"]);
a.expect_ok("rev-parse A");
std::fs::write(
work.join("src.txt"),
"line one\nLINE TWO CHANGED\nline three\n",
)
.expect("write");
harness
.git(work, &["commit", "--quiet", "-am", "B"])
.expect_ok("commit B");
let b = harness.git(work, &["rev-parse", "HEAD"]);
b.expect_ok("rev-parse B");
harness
.git(work, &["push", "--quiet", url, "main"])
.expect_ok("push");
(a.stdout.trim().to_owned(), b.stdout.trim().to_owned())
}
/// **Blame names the commit that wrote each line**, by the oid git reported —
/// line 2 is B's, lines 1 and 3 are A's.
#[test]
fn blame_names_the_commit_that_wrote_each_line() {
assert!(
tools_present(),
"this test requires `git` and `ssh` on PATH"
);
let mut harness = Harness::start("team", "blame", HashKind::Sha1);
let work = harness.dir.path().join("work");
let url = harness.url("team", "blame");
let (oid_a, oid_b) = two_commits(&harness, &work, &url);
let plane = gunnar_cli::server::CoreControlPlane::new(std::sync::Arc::clone(&harness.stores));
let repo = RepoPath::new("team", "blame");
let rows = blame(&harness.stores, &plane, &repo, "refs/heads/main", "src.txt").expect("blame");
assert_eq!(rows.len(), 3, "the file has three lines");
assert!(
oid_a.starts_with(&rows[0].commit.to_hex().to_string()[..12]),
"line 1 must be A ({oid_a}), got {}",
rows[0].commit
);
assert!(
oid_b.starts_with(&rows[1].commit.to_hex().to_string()[..12]),
"line 2 was rewritten by B ({oid_b}), got {}",
rows[1].commit
);
assert!(
oid_a.starts_with(&rows[2].commit.to_hex().to_string()[..12]),
"line 3 must be A ({oid_a}), got {}",
rows[2].commit
);
assert_eq!(rows[1].line, b"LINE TWO CHANGED");
harness.stop();
}
/// **Search finds a present string at its exact line, and an absent one returns
/// nothing** — not an error, not a false hit.
#[test]
fn search_finds_the_present_string_and_not_the_absent_one() {
assert!(
tools_present(),
"this test requires `git` and `ssh` on PATH"
);
let mut harness = Harness::start("team", "search", HashKind::Sha1);
let work = harness.dir.path().join("work");
let url = harness.url("team", "search");
two_commits(&harness, &work, &url);
let plane = gunnar_cli::server::CoreControlPlane::new(std::sync::Arc::clone(&harness.stores));
let repo = RepoPath::new("team", "search");
let hits = search(
&harness.stores,
&plane,
&repo,
"refs/heads/main",
"CHANGED",
Limits::default(),
)
.expect("search");
assert_eq!(hits.len(), 1, "the string is on exactly one line");
assert_eq!(hits[0].path, "src.txt");
assert_eq!(hits[0].line_number, 2);
let none = search(
&harness.stores,
&plane,
&repo,
"refs/heads/main",
"this string is nowhere in the tree",
Limits::default(),
)
.expect("a search for an absent string is not an error");
assert!(
none.is_empty(),
"an absent string yields zero hits, got {none:?}"
);
harness.stop();
}