Repository: mitsuhiko/insta
Pull request #722 (2025-02-12 09:28:28)

Fix inline snapshot handling within `allow_duplicates!` block
#712

There are two problems:
1. single-line snapshot isn't deduplicated
2. the last assertion in the `allow_duplicates!` block is always chosen

```rust
#[test]
fn test_dup() {
    for _ in 0..2 {
        insta::allow_duplicates! {
            // shouldn't be updated twice
            insta::assert_snapshot!("foo", @"");
        }
    }
}
```

```rust
#[test]
fn test_bad_location() {
    for _ in 0..2 {
        insta::allow_duplicates! {
            // each snapshot should be updated accordingly
            insta::assert_snapshot!("1", @"
            1a
            1b
            ");
            insta::assert_snapshot!("2", @"
            2a
            2b
            ");
        }
    }
}
```

ilyagr: Passes all of my tests, nice!
max-sixty: This looks very good, thank you @yuja !

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57f1e9a..247509c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ All notable changes to insta and cargo-insta are documented here.
 ## 1.42.2
 
 - Stop `\t` and `\x1b` (ANSI color escape) from causing snapshots to be escaped.  #715
+- Improved handling of inline snapshots within `allow_duplicates! { .. }`.  #712
 
 ## 1.42.1
 
diff --git a/cargo-insta/src/inline.rs b/cargo-insta/src/inline.rs
index 006f552..a44ba9d 100644
--- a/cargo-insta/src/inline.rs
+++ b/cargo-insta/src/inline.rs
@@ -76,7 +76,8 @@ impl FilePatcher {
                 if self
                     .inline_snapshots
                     .last()
-                    .map_or(false, |x| x.end.0 > line)
+                    // x.end.0 is 0-origin whereas line is 1-origin
+                    .map_or(false, |x| x.end.0 >= line - 1)
                 {
                     return false;
                 }
@@ -253,6 +254,18 @@ impl FilePatcher {
                     return;
                 }
 
+                // recurse into block-like macro such as allow_duplicates! { .. }
+                if matches!(i.delimiter, syn::MacroDelimiter::Brace(_)) {
+                    if let Ok(stmts) = i.parse_body_with(syn::Block::parse_within) {
+                        for stmt in &stmts {
+                            self.visit_stmt(stmt);
+                        }
+                        return;
+                    }
+                    // TODO: perhaps, we can return here and remove fallback to
+                    // self.scan_nested_macros(&tokens)
+                }
+
                 let indentation = indentation(span_start, self.2);
                 if !self.try_extract_snapshot(&tokens, indentation) {
                     // if we can't extract a snapshot here we want to scan for nested
@@ -357,4 +370,47 @@ fn test_function() {
         // Assert the indentation
         assert_debug_snapshot!(snapshot.indentation, @r#""\t""#);
     }
+
+    #[test]
+    fn test_find_snapshot_macro_within_allow_duplicates() {
+        let content = r######"
+fn test_function() {
+    insta::allow_duplicates! {
+        for x in 0..10 {
+            insta::assert_snapshot!("foo", @"foo"); // 5
+            insta::assert_snapshot!("bar", @"bar"); // 6
+        }
+    }
+}
+"######;
+
+        let file_patcher = FilePatcher {
+            filename: PathBuf::new(),
+            lines: content.lines().map(String::from).collect(),
+            source: syn::parse_file(content).unwrap(),
+            inline_snapshots: vec![],
+        };
+
+        let snapshot5 = file_patcher.find_snapshot_macro(5).unwrap();
+        let snapshot6 = file_patcher.find_snapshot_macro(6).unwrap();
+
+        // Extract the snapshot contents
+        let snapshot_content5 = file_patcher.lines[snapshot5.start.0..=snapshot5.end.0].to_vec();
+        let snapshot_content6 = file_patcher.lines[snapshot6.start.0..=snapshot6.end.0].to_vec();
+
+        assert_debug_snapshot!(snapshot_content5, @r#"
+        [
+            "            insta::assert_snapshot!(\"foo\", @\"foo\"); // 5",
+        ]
+        "#);
+        assert_debug_snapshot!(snapshot_content6, @r#"
+        [
+            "            insta::assert_snapshot!(\"bar\", @\"bar\"); // 6",
+        ]
+        "#);
+
+        // Assert the indentation
+        assert_debug_snapshot!(snapshot5.indentation, @r#""            ""#);
+        assert_debug_snapshot!(snapshot6.indentation, @r#""            ""#);
+    }
 }


Test patch:
diff --git a/cargo-insta/tests/functional/inline.rs b/cargo-insta/tests/functional/inline.rs
index 8a58679..edb9153 100644
--- a/cargo-insta/tests/functional/inline.rs
+++ b/cargo-insta/tests/functional/inline.rs
@@ -337,3 +337,146 @@ fn test_hashtag_escape() {
      }
     "####);
 }
+
+#[test]
+fn test_single_line_duplicates() {
+    let test_project = TestFiles::new()
+        .add_cargo_toml("test_single_line_duplicates")
+        .add_file(
+            "src/lib.rs",
+            r#####"
+#[test]
+fn test_single_line_duplicates() {
+    for _ in 0..2 {
+        insta::allow_duplicates! {
+            insta::assert_snapshot!("foo", @"");
+        }
+    }
+}
+"#####
+                .to_string(),
+        )
+        .create_project();
+
+    let output = test_project
+        .insta_cmd()
+        .args(["test", "--accept"])
+        .output()
+        .unwrap();
+
+    assert!(&output.status.success());
+
+    // diff shouldn't be applied twice
+    assert_snapshot!(test_project.diff("src/lib.rs"), @r#"
+    --- Original: src/lib.rs
+    +++ Updated: src/lib.rs
+    @@ -3,7 +3,7 @@
+     fn test_single_line_duplicates() {
+         for _ in 0..2 {
+             insta::allow_duplicates! {
+    -            insta::assert_snapshot!("foo", @"");
+    +            insta::assert_snapshot!("foo", @"foo");
+             }
+         }
+     }
+    "#);
+}
+
+#[test]
+fn test_single_line_assertions() {
+    let test_project = TestFiles::new()
+        .add_cargo_toml("test_single_line_assertions")
+        .add_file(
+            "src/lib.rs",
+            r#####"
+#[test]
+fn test_single_line_assertions() {
+    insta::assert_snapshot!("foo", @"");
+    insta::assert_snapshot!("bar", @"");
+}
+"#####
+                .to_string(),
+        )
+        .create_project();
+
+    let output = test_project
+        .insta_cmd()
+        .args(["test", "--accept"])
+        .output()
+        .unwrap();
+
+    assert!(&output.status.success());
+
+    // all adjacent lines should be updated
+    assert_snapshot!(test_project.diff("src/lib.rs"), @r#"
+    --- Original: src/lib.rs
+    +++ Updated: src/lib.rs
+    @@ -1,6 +1,6 @@
+     
+     #[test]
+     fn test_single_line_assertions() {
+    -    insta::assert_snapshot!("foo", @"");
+    -    insta::assert_snapshot!("bar", @"");
+    +    insta::assert_snapshot!("foo", @"foo");
+    +    insta::assert_snapshot!("bar", @"bar");
+     }
+    "#);
+}
+
+#[test]
+fn test_multiple_assertions_within_allow_duplicates() {
+    let test_project = TestFiles::new()
+        .add_cargo_toml("test_multiple_assertions_within_allow_duplicates")
+        .add_file(
+            "src/lib.rs",
+            r#####"
+#[test]
+fn test_multiple_assertions_within_allow_duplicates() {
+    for _ in 0..2 {
+        insta::allow_duplicates! {
+            insta::assert_snapshot!("1", @"
+            1a
+            1b
+            ");
+            insta::assert_snapshot!("2", @"
+            2a
+            2b
+            ");
+        }
+    }
+}
+"#####
+                .to_string(),
+        )
+        .create_project();
+
+    let output = test_project
+        .insta_cmd()
+        .args(["test", "--accept"])
+        .output()
+        .unwrap();
+
+    assert!(&output.status.success());
+
+    assert_snapshot!(test_project.diff("src/lib.rs"), @r#"
+    --- Original: src/lib.rs
+    +++ Updated: src/lib.rs
+    @@ -3,14 +3,8 @@
+     fn test_multiple_assertions_within_allow_duplicates() {
+         for _ in 0..2 {
+             insta::allow_duplicates! {
+    -            insta::assert_snapshot!("1", @"
+    -            1a
+    -            1b
+    -            ");
+    -            insta::assert_snapshot!("2", @"
+    -            2a
+    -            2b
+    -            ");
+    +            insta::assert_snapshot!("1", @"1");
+    +            insta::assert_snapshot!("2", @"2");
+             }
+         }
+     }
+    "#);
+}