From c9873817d5c069d6420473d0ace0c0cf84855855 Mon Sep 17 00:00:00 2001
From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com>
Date: Wed, 19 Mar 2025 21:31:18 +0000
Subject: [PATCH] test: create helper macros for tests

---
 helix-vcs/src/git/blame.rs | 90 ++++++++++++++++++++++++++++++++------
 helix-vcs/src/git/test.rs  | 26 ++++++-----
 2 files changed, 92 insertions(+), 24 deletions(-)

diff --git a/helix-vcs/src/git/blame.rs b/helix-vcs/src/git/blame.rs
index 0ee4deed..340cea96 100644
--- a/helix-vcs/src/git/blame.rs
+++ b/helix-vcs/src/git/blame.rs
@@ -4,7 +4,7 @@ use std::{collections::HashMap, path::Path};
 
 use super::{get_repo_dir, open_repo};
 
-#[derive(Clone, PartialEq, PartialOrd, Ord, Eq)]
+#[derive(Clone, PartialEq, PartialOrd, Ord, Eq, Debug)]
 pub struct BlameInformation {
     pub commit_hash: Option<String>,
     pub author_name: Option<String>,
@@ -133,7 +133,7 @@ pub fn blame_line(
     //
     // So when our cursor is on the 10th added line or earlier, blame_line will be 0. This means
     // the blame will be incorrect. But that's fine, because when the cursor_line is on some hunk,
-    // we can show to the user nothing at all
+    // we can show to the user nothing at all. This is detected in the editor
     let blame_line = line.saturating_sub(added_lines_count) + removed_lines_count;
 
     let repo_dir = get_repo_dir(file)?;
@@ -193,29 +193,93 @@ pub fn blame_line(
 
 #[cfg(test)]
 mod test {
+    use std::fs::File;
+
+    use crate::git::test::{create_commit_with_message, empty_git_repo};
+
     use super::*;
 
-    #[test]
-    pub fn inline_blame_parser() {
-        let bob = BlameInformation {
+    macro_rules! assert_blamed_lines {
+        ($repo:ident, $file:ident @ $($commit_msg:literal => $($line:literal $expected:literal),+);+ $(;)?) => {{
+            use std::fs::OpenOptions;
+            use std::io::Write;
+
+            let write_file = |content: &str| {
+                let mut f = OpenOptions::new()
+                    .write(true)
+                    .truncate(true)
+                    .open(&$file)
+                    .unwrap();
+                f.write_all(content.as_bytes()).unwrap();
+            };
+
+            let commit = |msg| create_commit_with_message($repo.path(), true, msg);
+
+            $(
+                let file_content = concat!($($line, "\n"),*);
+                write_file(file_content);
+                commit(stringify!($commit_msg));
+
+                let mut line_number = 0;
+
+                $(
+                    line_number += 1;
+                    let blame_result = blame_line(&$file, line_number, 0, 0).unwrap().commit_message;
+                    assert_eq!(
+                        blame_result,
+                        Some(concat!(stringify!($expected), "\n").to_owned()),
+                        "Blame mismatch at line {}: expected '{}', got {:?}",
+                        line_number,
+                        stringify!($expected),
+                        blame_result
+                    );
+                )*
+            )*
+        }};
+    }
+
+    fn bob() -> BlameInformation {
+        BlameInformation {
             commit_hash: Some("f14ab1cf".to_owned()),
             author_name: Some("Bob TheBuilder".to_owned()),
             author_email: Some("bob@bob.com".to_owned()),
             commit_date: Some("2028-01-10".to_owned()),
             commit_message: Some("feat!: extend house".to_owned()),
             commit_body: Some("BREAKING CHANGE: Removed door".to_owned()),
-        };
+        }
+    }
 
+    #[test]
+    pub fn blame_lin() {
+        let repo = empty_git_repo();
+        let file = repo.path().join("file.txt");
+        File::create(&file).unwrap();
+
+        assert_blamed_lines! {
+            repo, file @
+            1 =>
+                "fn main() {" 1,
+                "" 1,
+                "}" 1;
+            2 =>
+                "fn main() {" 1,
+                "  lol" 2,
+                "}" 1;
+        };
+    }
+
+    #[test]
+    pub fn inline_blame_format_parser() {
         let default_values = "{author}, {date} • {message} • {commit}";
 
         assert_eq!(
-            bob.parse_format(default_values),
+            bob().parse_format(default_values),
             "Bob TheBuilder, 2028-01-10 • feat!: extend house • f14ab1cf".to_owned()
         );
         assert_eq!(
             BlameInformation {
                 author_name: None,
-                ..bob.clone()
+                ..bob()
             }
             .parse_format(default_values),
             "2028-01-10 • feat!: extend house • f14ab1cf".to_owned()
@@ -223,7 +287,7 @@ mod test {
         assert_eq!(
             BlameInformation {
                 commit_date: None,
-                ..bob.clone()
+                ..bob()
             }
             .parse_format(default_values),
             "Bob TheBuilder • feat!: extend house • f14ab1cf".to_owned()
@@ -232,7 +296,7 @@ mod test {
             BlameInformation {
                 commit_message: None,
                 author_email: None,
-                ..bob.clone()
+                ..bob()
             }
             .parse_format(default_values),
             "Bob TheBuilder, 2028-01-10 • f14ab1cf".to_owned()
@@ -240,7 +304,7 @@ mod test {
         assert_eq!(
             BlameInformation {
                 commit_hash: None,
-                ..bob.clone()
+                ..bob()
             }
             .parse_format(default_values),
             "Bob TheBuilder, 2028-01-10 • feat!: extend house".to_owned()
@@ -249,7 +313,7 @@ mod test {
             BlameInformation {
                 commit_date: None,
                 author_name: None,
-                ..bob.clone()
+                ..bob()
             }
             .parse_format(default_values),
             "feat!: extend house • f14ab1cf".to_owned()
@@ -258,7 +322,7 @@ mod test {
             BlameInformation {
                 author_name: None,
                 commit_message: None,
-                ..bob.clone()
+                ..bob()
             }
             .parse_format(default_values),
             "2028-01-10 • f14ab1cf".to_owned()
diff --git a/helix-vcs/src/git/test.rs b/helix-vcs/src/git/test.rs
index 164040f5..c758c80b 100644
--- a/helix-vcs/src/git/test.rs
+++ b/helix-vcs/src/git/test.rs
@@ -4,11 +4,11 @@ use tempfile::TempDir;
 
 use crate::git;
 
-fn exec_git_cmd(args: &str, git_dir: &Path) {
+pub fn exec_git_cmd(args: &[&str], git_dir: &Path) {
     let res = Command::new("git")
         .arg("-C")
         .arg(git_dir) // execute the git command in this directory
-        .args(args.split_whitespace())
+        .args(args)
         .env_remove("GIT_DIR")
         .env_remove("GIT_ASKPASS")
         .env_remove("SSH_ASKPASS")
@@ -25,26 +25,30 @@ fn exec_git_cmd(args: &str, git_dir: &Path) {
         .env("GIT_CONFIG_KEY_1", "init.defaultBranch")
         .env("GIT_CONFIG_VALUE_1", "main")
         .output()
-        .unwrap_or_else(|_| panic!("`git {args}` failed"));
+        .unwrap_or_else(|_| panic!("`git {args:?}` failed"));
     if !res.status.success() {
         println!("{}", String::from_utf8_lossy(&res.stdout));
         eprintln!("{}", String::from_utf8_lossy(&res.stderr));
-        panic!("`git {args}` failed (see output above)")
+        panic!("`git {args:?}` failed (see output above)")
     }
 }
 
-fn create_commit(repo: &Path, add_modified: bool) {
+pub fn create_commit(repo: &Path, add_modified: bool) {
+    create_commit_with_message(repo, add_modified, "commit")
+}
+
+pub fn create_commit_with_message(repo: &Path, add_modified: bool, message: &str) {
     if add_modified {
-        exec_git_cmd("add -A", repo);
+        exec_git_cmd(&["add", "-A"], repo);
     }
-    exec_git_cmd("commit -m message", repo);
+    exec_git_cmd(&["commit", "-m", message], repo);
 }
 
-fn empty_git_repo() -> TempDir {
+pub fn empty_git_repo() -> TempDir {
     let tmp = tempfile::tempdir().expect("create temp dir for git testing");
-    exec_git_cmd("init", tmp.path());
-    exec_git_cmd("config user.email test@helix.org", tmp.path());
-    exec_git_cmd("config user.name helix-test", tmp.path());
+    exec_git_cmd(&["init"], tmp.path());
+    exec_git_cmd(&["config", "user.email", "test@helix.org"], tmp.path());
+    exec_git_cmd(&["config", "user.name", "helix-test"], tmp.path());
     tmp
 }