From d553b81aa7d88d6f17e3ff037498564fb68a02fd Mon Sep 17 00:00:00 2001
From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com>
Date: Wed, 19 Mar 2025 23:05:44 +0000
Subject: [PATCH] test: make test syntax more expressive. Allow specifying line
 numbers that just got added

---
 helix-vcs/src/git/blame.rs | 141 ++++++++++++++++++++++++++++---------
 1 file changed, 108 insertions(+), 33 deletions(-)

diff --git a/helix-vcs/src/git/blame.rs b/helix-vcs/src/git/blame.rs
index 340cea96..bb626db7 100644
--- a/helix-vcs/src/git/blame.rs
+++ b/helix-vcs/src/git/blame.rs
@@ -191,6 +191,12 @@ pub fn blame_line(
     })
 }
 
+// attributes on expressions are not allowed
+// however, in our macro its possible that sometimes the
+// assignment to the mutable variable will not be read.
+//
+// when the last line has no expected blame commit
+#[allow(unused_assignments)]
 #[cfg(test)]
 mod test {
     use std::fs::File;
@@ -199,45 +205,133 @@ mod test {
 
     use super::*;
 
-    macro_rules! assert_blamed_lines {
-        ($repo:ident, $file:ident @ $($commit_msg:literal => $($line:literal $expected:literal),+);+ $(;)?) => {{
+    macro_rules! no_commit_flag {
+        (no_commit, $commit_msg:literal) => {
+            false
+        };
+        (, $commit_msg:literal) => {
+            true
+        };
+        ($any:tt, $commit_msg:literal) => {
+            compile_error!(concat!(
+                "expected no_commit or nothing for commit ",
+                $commit_msg
+            ))
+        };
+    }
+
+    macro_rules! add_flag {
+        (add, $commit_msg:literal, $line:expr) => {
+            true
+        };
+        (, $commit_msg:literal, $line:expr) => {
+            false
+        };
+        ($any:tt, $commit_msg:literal, $line:expr) => {
+            compile_error!(concat!(
+                "expected no_commit or nothing for commit ",
+                $commit_msg,
+                " line ",
+                $line
+            ))
+        };
+    }
+
+    /// Helper macro to create a history of the same file being modified.
+    ///
+    /// Each $commit_msg is a unique identifier for a commit message.
+    /// Each $line is a string line of the file. These $lines are collected into a single String
+    /// which then becomes the new contents of the $file
+    ///
+    /// Each $line gets blamed using blame_line. The $expected is the commit identifier that we are expecting for that line.
+    macro_rules! assert_line_blame_progress {
+        ($($commit_msg:literal $($no_commit:ident)? => $($line:literal $($expected:literal)? $($added:ident)? ),+);+ $(;)?) => {{
             use std::fs::OpenOptions;
             use std::io::Write;
 
+            let repo = empty_git_repo();
+            let file = repo.path().join("file.txt");
+            File::create(&file).expect("could not create file");
+
             let write_file = |content: &str| {
                 let mut f = OpenOptions::new()
                     .write(true)
                     .truncate(true)
-                    .open(&$file)
+                    .open(&file)
                     .unwrap();
                 f.write_all(content.as_bytes()).unwrap();
             };
 
-            let commit = |msg| create_commit_with_message($repo.path(), true, msg);
+            let commit = |msg| create_commit_with_message(repo.path(), true, msg);
 
             $(
                 let file_content = concat!($($line, "\n"),*);
+                eprintln!("at commit {}:\n\n{file_content}", stringify!($commit_msg));
                 write_file(file_content);
-                commit(stringify!($commit_msg));
+
+                let should_commit = no_commit_flag!($($no_commit)?, $commit_msg);
+                if should_commit {
+                    commit(stringify!($commit_msg));
+                }
 
                 let mut line_number = 0;
+                let mut added_lines = 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
-                    );
+                    let has_add_flag = add_flag!($($added)?, $commit_msg, $line);
+                    if has_add_flag {
+                        added_lines += 1;
+                    }
+                    // if there is no $expected, then we don't care what blame_line returns
+                    // because we won't show it to the user.
+                    $(
+
+                        let blame_result = blame_line(&file, line_number, added_lines, 0).unwrap().commit_message;
+                        assert_eq!(
+                            blame_result,
+                            Some(concat!(stringify!($expected), "\n").to_owned()),
+                            "Blame mismatch\nat commit: {}\nat line: {}\nline contents: {}\nexpected commit: {}\nbut got commit: {}",
+                            $commit_msg,
+                            line_number,
+                            file_content.lines().nth(line_number.try_into().unwrap()).unwrap(),
+                            stringify!($expected),
+                            blame_result.as_ref().map(|blame| blame.trim_end()).unwrap_or("<no commit>")
+                        );
+                    )?
                 )*
             )*
         }};
     }
 
+    #[test]
+    pub fn blamed_lines() {
+        assert_line_blame_progress! {
+            1 =>
+                "fn main() {" 1,
+                "" 1,
+                "}" 1;
+            2 =>
+                "fn main() {" 1,
+                "  one" 2,
+                "}" 1;
+            3 =>
+                "fn main() {" 1,
+                "  one" 2,
+                "  two" 3,
+                "}" 1;
+            4 =>
+                "fn main() {" 1,
+                "  two" 3,
+                "}" 1;
+            5 no_commit =>
+                "fn main() {" 1,
+                "  hello world" add,
+                "  two" 3,
+                "}" 1;
+        };
+    }
+
     fn bob() -> BlameInformation {
         BlameInformation {
             commit_hash: Some("f14ab1cf".to_owned()),
@@ -249,25 +343,6 @@ mod test {
         }
     }
 
-    #[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}";