Repository: mitsuhiko/insta
Pull request #524 (2024-07-07 21:41:47)

Add `cargo.lock`, use `cargo-msrv` to verify MSRV
This commit does a few things:
- Adds `Cargo.lock` to the repo. Maybe others have different opinions; though it is [now recommended](https://github.com/rust-lang/cargo/commit/3bb02f23a23f90261b44a107e1b8821a504bac08#diff-3388c611c3bcae8ef4efe0ec5c9f5e22682b0db86c9e7137e70e5ba7d5e456c0R11), and it solves a bunch of the issues around `Cargo.lock.msrv`, closing #498. Without this, the remainder of this PR isn't feasible in its current state.
- Uses `cargo-msrv` to verify the MSRV specs in the `Cargo.toml` files in CI, simplifying CI jobs
- Adds 1.61.0 as the MSRV for `cargo-insta`
- Bumps the MSRV for `insta` to 1.57.0, from 1.51.0, allowing us to remove some of the hacks around named profiles. 1.57.0 was released Dec 2021.



Patch:
diff --git a/.gitignore b/.gitignore
index d75dfc2..9656d14 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
 target
 **/*.rs.bk
-/Cargo.lock
 .vscode/*.log
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0b48dcc..51d044b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,7 +3,7 @@ repos:
     rev: v4.6.0
     hooks:
       - id: end-of-file-fixer
-        exclude: '(.*\.snap)'
+        exclude: '(.*\.snap)|(insta/tests/inputs/.*)'
       - id: check-yaml
       - id: mixed-line-ending
   - repo: https://github.com/crate-ci/typos
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1c6be9..bea8c63 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ All notable changes to insta and cargo-insta are documented here.
 
 ## 1.40.0
 
+- Print a warning when encountering old snapshot formats.  #503
+
 - No longer suggest running `cargo insta` message when running `cargo insta test --check`.  #515
 
 - Print a clearer error message when accepting a snapshot that was removed.  #516
@@ -12,6 +14,8 @@ All notable changes to insta and cargo-insta are documented here.
 
 - Mark `require-full-match` as experimental, given some corner-cases are currently difficult to manage.  #497
 
+- `cargo insta test` now accepts multiple `--exclude` flags.  #520
+
 ## 1.39.0
 
 - Fixed a bug in `require_full_match`.  #485
diff --git a/Cargo.lock.msrv b/Cargo.lock
similarity index 98%
rename from Cargo.lock.msrv
rename to Cargo.lock
index 3528d98..070391e 100644
--- a/Cargo.lock.msrv
+++ b/Cargo.lock
@@ -55,7 +55,7 @@ dependencies = [
 
 [[package]]
 name = "cargo-insta"
-version = "1.37.0"
+version = "1.39.0"
 dependencies = [
  "cargo_metadata",
  "console",
@@ -302,7 +302,7 @@ dependencies = [
 
 [[package]]
 name = "insta"
-version = "1.37.0"
+version = "1.39.0"
 dependencies = [
  "console",
  "csv",
@@ -313,6 +313,7 @@ dependencies = [
  "pest_derive",
  "regex",
  "ron",
+ "rustc_version",
  "serde",
  "similar",
  "similar-asserts",
@@ -526,6 +527,15 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
 [[package]]
 name = "rustix"
 version = "0.37.19"
diff --git a/Makefile b/Makefile
index f1bc71a..26590d1 100644
--- a/Makefile
+++ b/Makefile
@@ -27,6 +27,11 @@ check-minver:
 	@cargo minimal-versions check -p insta --no-default-features
 	@cargo minimal-versions check -p insta --features redactions
 
+check-msrv:
+	@echo "MSRV CHECK"
+	@cd insta && cargo msrv verify
+	@cd cargo-insta && cargo msrv verify
+
 format:
 	@rustup component add rustfmt 2> /dev/null
 	@cargo fmt --all
diff --git a/cargo-insta/Cargo.toml b/cargo-insta/Cargo.toml
index 5ffa1f6..9c846e7 100644
--- a/cargo-insta/Cargo.toml
+++ b/cargo-insta/Cargo.toml
@@ -11,6 +11,7 @@ keywords = ["snapshot", "testing", "jest"]
 categories = ["development-tools::cargo-plugins"]
 edition = "2018"
 readme = "README.md"
+rust-version = "1.61.0"
 
 [dependencies]
 insta = { version = "=1.39.0", path = "../insta", features = ["json", "yaml", "redactions", "_cargo_insta_internal"] }
@@ -19,8 +20,8 @@ console = "0.15.4"
 structopt = { version = "0.3.26", default-features = false }
 serde = { version = "1.0.117", features = ["derive"] }
 serde_json = "1.0.59"
-proc-macro2 = { version = "1.0.24", features = ["span-locations"] }
-syn = { version = "1.0.50", features = ["full", "visit", "extra-traits"] }
+proc-macro2 = { version = "1.0.60", features = ["span-locations"] }
+syn = { version = "2.0.0", features = ["full", "visit", "extra-traits"] }
 ignore = "0.4.17"
 uuid = { version = "1.0.0", features = ["v4"] }
 tempfile = "3.5.0"
diff --git a/cargo-insta/src/cli.rs b/cargo-insta/src/cli.rs
index db47ed0..23f2b20 100644
--- a/cargo-insta/src/cli.rs
+++ b/cargo-insta/src/cli.rs
@@ -137,7 +137,7 @@ struct TestCommand {
     package: Vec<String>,
     /// Exclude packages from the test
     #[structopt(long, value_name = "SPEC")]
-    exclude: Option<String>,
+    exclude: Vec<String>,
     /// Disable force-passing of snapshot tests
     #[structopt(long)]
     no_force_pass: bool,
@@ -866,7 +866,7 @@ fn prepare_test_runner<'snapshot_ref>(
         proc.arg("--package");
         proc.arg(pkg);
     }
-    if let Some(ref spec) = cmd.exclude {
+    for spec in &cmd.exclude {
         proc.arg("--exclude");
         proc.arg(spec);
     }
diff --git a/cargo-insta/src/inline.rs b/cargo-insta/src/inline.rs
index 803fcc3..77b73a7 100644
--- a/cargo-insta/src/inline.rs
+++ b/cargo-insta/src/inline.rs
@@ -7,6 +7,7 @@ use std::path::{Path, PathBuf};
 use insta::_cargo_insta_support::SnapshotContents;
 use proc_macro2::TokenTree;
 
+use syn::__private::ToTokens;
 use syn::spanned::Spanned;
 
 #[derive(Debug)]
@@ -213,21 +214,17 @@ impl FilePatcher {
         impl<'ast> syn::visit::Visit<'ast> for Visitor {
             fn visit_attribute(&mut self, i: &'ast syn::Attribute) {
                 let start = i.span().start().line;
-                let end = i
-                    .tokens
-                    .clone()
-                    .into_iter()
-                    .last()
-                    .map_or(start, |t| t.span().end().line);
+                let end = i.span().end().line;
 
-                if start > self.0 || end < self.0 || i.path.segments.is_empty() {
+                if start > self.0 || end < self.0 || i.path().segments.is_empty() {
                     return;
                 }
 
-                let tokens: Vec<_> = i.tokens.clone().into_iter().collect();
-                self.scan_nested_macros(&tokens);
+                let tokens: Vec<_> = i.meta.to_token_stream().into_iter().collect();
+                if !tokens.is_empty() {
+                    self.scan_nested_macros(&tokens);
+                }
             }
-
             fn visit_macro(&mut self, i: &'ast syn::Macro) {
                 let indentation = i.span().start().column;
                 let start = i.span().start().line;
diff --git a/insta/Cargo.toml b/insta/Cargo.toml
index 29372a0..1798b7d 100644
--- a/insta/Cargo.toml
+++ b/insta/Cargo.toml
@@ -5,7 +5,7 @@ license = "Apache-2.0"
 authors = ["Armin Ronacher <armin.ronacher@active-4.com>"]
 description = "A snapshot testing library for Rust"
 edition = "2018"
-rust-version = "1.51.0"
+rust-version = "1.57.0"
 homepage = "https://insta.rs/"
 repository = "https://github.com/mitsuhiko/insta"
 keywords = ["snapshot", "testing", "jest", "approval"]
diff --git a/insta/src/content/serialization.rs b/insta/src/content/serialization.rs
index 68d4bb4..ae4e453 100644
--- a/insta/src/content/serialization.rs
+++ b/insta/src/content/serialization.rs
@@ -20,6 +20,9 @@ pub enum Key<'a> {
 
 impl<'a> Eq for Key<'a> {}
 
+// We're making a deliberate choice to just "round down" here, so ignoring the
+// clippy lint
+#[allow(clippy::derive_ord_xor_partial_ord)]
 impl<'a> Ord for Key<'a> {
     fn cmp(&self, other: &Self) -> Ordering {
         self.partial_cmp(other).unwrap_or(Ordering::Less)
diff --git a/insta/src/lib.rs b/insta/src/lib.rs
index 9820515..e23b80f 100644
--- a/insta/src/lib.rs
+++ b/insta/src/lib.rs
@@ -62,15 +62,7 @@
 //!
 //! Note that `cargo-insta` is entirely optional.  You can also just use insta
 //! directly from `cargo test` and control it via the `INSTA_UPDATE` environment
-//! variable.  The default is `auto` which will write all new snapshots into
-//! `.snap.new` files if no CI is detected so that `cargo-insta` can pick them
-//! up.  The following other modes are possible:
-//!
-//! - `auto`: the default. `no` for CI environments or `new` otherwise
-//! - `always`: overwrites old snapshot files with new ones unasked
-//! - `unseen`: behaves like `always` for new snapshots and `new` for others
-//! - `new`: write new snapshots into `.snap.new` files
-//! - `no`: does not update snapshot files at all (just runs tests)
+//! variable — see [Updating snapshots](#updating-snapshots) for details.
 //!
 //! You can for instance first run the tests and not write and new snapshots, and
 //! if you like them run the tests again and update them:
@@ -119,7 +111,7 @@
 //! partial values.  See [redactions in the
 //! documentation](https://insta.rs/docs/redactions/) for more information.
 //!
-//! # Snapshot updating
+//! # Updating snapshots
 //!
 //! During test runs snapshots will be updated according to the `INSTA_UPDATE`
 //! environment variable.  The default is `auto` which will write snapshots for
diff --git a/insta/src/macros.rs b/insta/src/macros.rs
index 93220f1..7ffdb06 100644
--- a/insta/src/macros.rs
+++ b/insta/src/macros.rs
@@ -297,7 +297,9 @@ macro_rules! _prepare_snapshot_for_redaction {
 #[macro_export]
 macro_rules! _prepare_snapshot_for_redaction {
     ($value:expr, {$($k:expr => $v:expr),*}, $format:ident, $location:ident) => {
-        compile_error!("insta was compiled without redaction support.");
+        compile_error!(
+            "insta was compiled without redactions support. Enable the `redactions` feature."
+        );
     };
 }
 
@@ -315,6 +317,20 @@ macro_rules! assert_debug_snapshot {
     };
 }
 
+/// Asserts a `Debug` snapshot in compact format.
+///
+/// The value needs to implement the `fmt::Debug` trait.  This is useful for
+/// simple values that do not implement the `Serialize` trait, but does not
+/// permit redactions.
+///
+/// Debug is called with `"{:?}"`, which means this does not use pretty-print.
+#[macro_export]
+macro_rules! assert_compact_debug_snapshot {
+    ($($arg:tt)*) => {
+        $crate::_assert_snapshot_base!(transform=|v| std::format!("{:?}", v), $($arg)*)
+    };
+}
+
 // A helper macro which takes a closure as `transform`, and runs the closure on
 // the value. This allows us to implement other macros with a small wrapper. All
 // snapshot macros eventually call this macro.
diff --git a/insta/src/snapshot.rs b/insta/src/snapshot.rs
index 79a46b2..7b9e6d6 100644
--- a/insta/src/snapshot.rs
+++ b/insta/src/snapshot.rs
@@ -247,20 +247,15 @@ impl MetaData {
         Content::Struct("MetaData", fields)
     }
 
-    /// Trims the metadata of fields that we don't save to `.snap` files; we
-    /// only use for display while reviewing
+    /// Trims the metadata of fields that we don't save to `.snap` files (those
+    /// we only use for display while reviewing)
     fn trim_for_persistence(&self) -> Cow<'_, MetaData> {
-        let is_inline = self.input_file.is_none();
-        // If it's inline, we don't persist any metadata
-        if is_inline {
-            return Cow::Owned(MetaData {
-                source: None,
-                assertion_line: None,
-                input_file: None,
-                expression: None,
-                ..self.clone()
-            });
-        }
+        // TODO: in order for `--require-full-match` to work on inline snapshots
+        // without cargo-insta, we need to trim all fields if there's an inline
+        // snapshot. But we don't know that from here (notably
+        // `self.input_file.is_none()` is not a correct approach). Given that
+        // `--require-full-match` is experimental and we're working on making
+        // inline & file snapshots more coherent, I'm leaving this as is for now.
         if self.assertion_line.is_some() {
             let mut rv = self.clone();
             rv.assertion_line = None;
@@ -324,6 +319,7 @@ impl Snapshot {
                     }
                 }
             }
+            eprintln!("A snapshot uses an old snapshot format; please update it to the new format with `cargo insta --force-update-snapshots.\n\nSnapshot is at: {}", p.to_string_lossy());
             rv
         };
 
@@ -465,11 +461,19 @@ impl Snapshot {
         let serialized_snapshot = self.serialize_snapshot(md);
 
         // check the reference file for contents.  Note that we always want to
-        // compare snapshots that were trimmed to persistence here.
+        // compare snapshots that were trimmed to persistence here.  This is a
+        // stricter check than even `matches_fully`, since it's comparing the
+        // exact contents of the file.
         if let Ok(old) = fs::read_to_string(ref_file.unwrap_or(path)) {
             let persisted = match md.trim_for_persistence() {
                 Cow::Owned(trimmed) => Cow::Owned(self.serialize_snapshot(&trimmed)),
-                Cow::Borrowed(_) => Cow::Borrowed(&serialized_snapshot),
+                Cow::Borrowed(trimmed) => {
+                    // This condition needs to hold, otherwise we need to
+                    // compare the old value to a newly trimmed serialized snapshot
+                    debug_assert_eq!(trimmed, md);
+
+                    Cow::Borrowed(&serialized_snapshot)
+                }
             };
             if old == persisted.as_str() {
                 return Ok(false);
@@ -681,6 +685,8 @@ fn get_inline_snapshot_value(frozen_value: &str) -> String {
     // (the only call site)
 
     if frozen_value.trim_start().starts_with('⋮') {
+        eprintln!("A snapshot uses an old snapshot format; please update it to the new format with `cargo insta --force-update-snapshots.\n\nValue: {}", frozen_value);
+
         // legacy format - retain so old snapshots still work
         let mut buf = String::new();
         let mut line_iter = frozen_value.lines();
@@ -955,7 +961,7 @@ fn test_parse_yaml_error() {
 
     let error = format!("{}", Snapshot::from_file(temp.as_path()).unwrap_err());
     assert!(error.contains("Failed parsing the YAML from"));
-    assert!(error.contains("/bad.yaml"));
+    assert!(error.contains("bad.yaml"));
 }
 
 /// Check that snapshots don't take ownership of the value
diff --git a/vscode-insta/.vscode/launch.json b/vscode-insta/.vscode/launch.json
index 0e191b5..e64ce7a 100644
--- a/vscode-insta/.vscode/launch.json
+++ b/vscode-insta/.vscode/launch.json
@@ -14,4 +14,4 @@
             ]
         }
     ]
-}
\ No newline at end of file
+}
diff --git a/vscode-insta/.vscode/settings.json b/vscode-insta/.vscode/settings.json
index ae67c4b..27a0194 100644
--- a/vscode-insta/.vscode/settings.json
+++ b/vscode-insta/.vscode/settings.json
@@ -6,4 +6,4 @@
   "[typescript]": {
     "editor.tabSize": 2
   }
-}
\ No newline at end of file
+}
diff --git a/vscode-insta/language-configuration.json b/vscode-insta/language-configuration.json
index 5fec58c..ecce826 100644
--- a/vscode-insta/language-configuration.json
+++ b/vscode-insta/language-configuration.json
@@ -24,4 +24,4 @@
         ["\"", "\""],
         ["'", "'"]
     ]
-}
\ No newline at end of file
+}
diff --git a/vscode-insta/syntaxes/insta-snapshots.tmLanguage.json b/vscode-insta/syntaxes/insta-snapshots.tmLanguage.json
index 72e2a41..515d16f 100644
--- a/vscode-insta/syntaxes/insta-snapshots.tmLanguage.json
+++ b/vscode-insta/syntaxes/insta-snapshots.tmLanguage.json
@@ -679,4 +679,4 @@
     }
   },
   "scopeName": "source.insta-snapshots"
-}
\ No newline at end of file
+}
diff --git a/vscode-insta/tsconfig.json b/vscode-insta/tsconfig.json
index 00e0d37..ca32d24 100644
--- a/vscode-insta/tsconfig.json
+++ b/vscode-insta/tsconfig.json
@@ -20,4 +20,4 @@
     "node_modules",
     ".vscode-test"
   ]
-}
\ No newline at end of file
+}


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index ad6849c..f80ada1 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -10,8 +10,13 @@ env:
 jobs:
   test-latest:
     name: Test on Latest
-    runs-on: ubuntu-latest
-
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os:
+          - ubuntu-latest
+          - macos-latest
+          - windows-latest
     steps:
       - uses: actions/checkout@v4
       - uses: dtolnay/rust-toolchain@master
@@ -21,37 +26,46 @@ jobs:
       - name: Test
         run: make test
 
-  test-stable:
-    name: Test on 1.61.0
+  test-current-cargo-insta:
+    name: Run the in-tree version of cargo-insta on ourselves
     runs-on: ubuntu-latest
 
     steps:
       - uses: actions/checkout@v4
       - uses: dtolnay/rust-toolchain@master
         with:
-          toolchain: 1.61.0
+          toolchain: stable
       - uses: Swatinem/rust-cache@v2
-      - name: Use Cargo.lock.msrv
-        run: cp Cargo.lock.msrv Cargo.lock
       - name: Test
-        run: make cargotest
+        run: cargo run -p cargo-insta -- test
 
   build-old-stable:
-    name: Check on 1.51.0
+    name: Check on MSRV
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: baptiste0928/cargo-install@v3
+        with:
+          crate: cargo-msrv
+      - uses: Swatinem/rust-cache@v2
+      - name: Verify minimum rust version
+        run: make check-msrv
+
+  test-latest-dependencies:
+    name: Test latest deps
+    # Note that we don't test MSRV after running `cargo update`, since that
+    # would increase our maintenance burden — we'd need to be constantly pinning
+    # sub-dependencies
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
       - uses: dtolnay/rust-toolchain@master
         with:
-          toolchain: 1.51.0
+          toolchain: stable
       - uses: Swatinem/rust-cache@v2
-      - name: Use Cargo.lock.msrv
-        run: cp Cargo.lock.msrv Cargo.lock
-      - name: Remove named profile
-        run: |
-          perl -ni -e 'print unless /\[profile\.dist\]/ .. eof' Cargo.toml
-      - name: Check
-        run: cargo check -p insta --no-default-features
+      - run: cargo update
+      - name: Test
+        run: make test
 
   build-docs:
     name: Build docs
diff --git a/insta/tests/test_inline.rs b/insta/tests/test_inline.rs
index 15877ac..240ca46 100644
--- a/insta/tests/test_inline.rs
+++ b/insta/tests/test_inline.rs
@@ -9,7 +9,7 @@ use insta::assert_yaml_snapshot;
 #[cfg(feature = "json")]
 use insta::{assert_compact_json_snapshot, assert_json_snapshot};
 
-use insta::{assert_debug_snapshot, assert_snapshot};
+use insta::{assert_compact_debug_snapshot, assert_debug_snapshot, assert_snapshot};
 use std::thread;
 
 #[test]
@@ -281,6 +281,12 @@ fn test_compact_json() {
     "###);
 }
 
+#[test]
+fn test_compact_debug() {
+    assert_compact_debug_snapshot!((1..30).collect::<Vec<_>>(), @"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]");
+    assert_compact_debug_snapshot!((1..34).collect::<Vec<_>>(), @"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]");
+}
+
 #[test]
 #[should_panic = "Insta does not allow inline snapshot assertions in loops"]
 fn test_inline_test_in_loop() {