Repository: mitsuhiko/insta
Pull request #660 (2024-10-12 03:44:00)

Defer to `cargo metadata` to evaluate the workspace root
Not a big deal, but I think we can just defer



Patch:
diff --git a/.gitignore b/.gitignore
index 9656d14..57b6931 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
 target
 **/*.rs.bk
 .vscode/*.log
+.aider*
+.env
+.idea
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be1b18b..4305e18 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,20 +2,38 @@
 
 All notable changes to insta and cargo-insta are documented here.
 
+## [Unreleased]
+
+- Pending snapshots are no longer removed throughout the workspace by
+  `cargo-insta` before running tests.  Instead, running a test will overwrite or
+  remove its own pending snapshot.  To remove all pending snapshots, use `cargo
+  insta reject` or run tests with `--unreferenced=delete`.  #651
+- `insta::internals::SettingsBindDropGuard` (returned from
+  `Settings::bind_to_scope`) no longer implements `Send`. This was an error and
+  any tests relying on this behavior where not working properly.
+  Fixes #694 in #695 by @jalil-salame
+
+## 1.41.1
+
+- Re-release of 1.41.1 to generate release artifacts correctly.
+
 ## 1.41.0
 
 - Experimental support for binary snapshots.  #610 (Florian Plattner)
 
-- `--force-update-snapshots` now writes every snapshot, regardless of whether
-  `insta` evaluates a write is required, and now implies `--accept`.  This
-  allows for `--force-update-snapshots` to update inline snapshots when
-  delimiters or indents require updating.
+- `--force-update-snapshots` now causes `cargo-insta` to write every snapshot, regardless of whether
+  snapshots fully match, and now implies `--accept`.  This
+  allows for `--force-update-snapshots` to update inline snapshots'
+  delimiters and indentation.
 
-  For the existing behavior of limiting writes which `insta` can evaluate are
-  required, use `--require-full-match`.  The main difference between
-  `--require-full-match` and the existing behavior of `--force-update-snapshots`
-  is that the test run will return a non-zero exit code if any snapshots don't
-  match fully.  #644
+  For the previous behavior of `--force-update-snapshots`, which limited writes to
+  snapshots which didn't fully match, use `--require-full-match`.
+  The main difference between `--require-full-match` and the existing behavior of `--force-update-snapshots`
+  is a non-zero exit code on any snapshots which don't fully match.
+
+  Like the previous behavior of `--force-update-snapshots`, `--require-full-match`
+  doesn't track inline snapshots' delimiters or
+  indentation, so can't update if those don't match.  #644
 
 - Inline snapshots only use `#` characters as delimiters when required.  #603
 
@@ -26,10 +44,15 @@ All notable changes to insta and cargo-insta are documented here.
 
 - Warnings are printed when any snapshot uses a legacy format.  #599
 
+- `cargo insta --version` now prints a version.  #665
+
 - `insta` now internally uses `INSTA_UPDATE=force` rather than
-  `INSTA_FORCE_UPDATE`.  (This doesn't affect users of `cargo-insta`, which
+  `INSTA_FORCE_UPDATE=1`.  (This doesn't affect users of `cargo-insta`, which
   handles this internally.)  #482
 
+- `cargo-insta`'s integration tests continue to grow over the past couple of versions,
+  and now offer coverage of most of `cargo-insta`'s interface.
+
 ## 1.40.0
 
 - `cargo-insta` no longer panics when running `cargo insta test --accept --workspace`
diff --git a/Cargo.lock b/Cargo.lock
index 70897cf..2835925 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -66,7 +66,7 @@ dependencies = [
 
 [[package]]
 name = "cargo-insta"
-version = "1.41.0"
+version = "1.41.1"
 dependencies = [
  "cargo_metadata",
  "clap",
@@ -74,7 +74,6 @@ dependencies = [
  "ignore",
  "insta",
  "itertools",
- "lazy_static",
  "open",
  "os_pipe",
  "proc-macro2",
@@ -354,14 +353,14 @@ dependencies = [
 
 [[package]]
 name = "insta"
-version = "1.41.0"
+version = "1.41.1"
 dependencies = [
  "clap",
  "console",
  "csv",
  "globset",
- "lazy_static",
  "linked-hash-map",
+ "once_cell",
  "pest",
  "pest_derive",
  "regex",
@@ -486,12 +485,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
 name = "once_cell"
-version = "1.20.1"
+version = "1.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1"
-dependencies = [
- "portable-atomic",
-]
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
 
 [[package]]
 name = "open"
@@ -506,12 +502,12 @@ dependencies = [
 
 [[package]]
 name = "os_pipe"
-version = "0.9.2"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213"
+checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
 dependencies = [
  "libc",
- "winapi",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -565,12 +561,6 @@ dependencies = [
  "sha2",
 ]
 
-[[package]]
-name = "portable-atomic"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
-
 [[package]]
 name = "proc-macro2"
 version = "1.0.86"
@@ -959,6 +949,15 @@ dependencies = [
  "windows-targets 0.52.6",
 ]
 
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
 [[package]]
 name = "windows-targets"
 version = "0.42.2"
diff --git a/Cargo.toml b/Cargo.toml
index 7164d07..50d664d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,13 @@ install-updater = false
 installers = ["shell", "powershell"]
 pr-run-mode = "plan"
 precise-builds = true
-targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"]
+targets = [
+    "aarch64-apple-darwin",
+    "x86_64-apple-darwin",
+    "x86_64-unknown-linux-gnu",
+    "x86_64-unknown-linux-musl",
+    "x86_64-pc-windows-msvc",
+]
 
 [profile.dist]
 inherits = "release"
@@ -17,4 +23,4 @@ lto = "thin"
 
 [workspace.dependencies]
 # Needs pinning in Cargo.lock because of MSRV
-clap = {version = "4.1", features = ["derive", "env"]}
+clap = { version = "4.1", features = ["derive", "env"] }
diff --git a/cargo-insta/Cargo.toml b/cargo-insta/Cargo.toml
index 044eddf..8b77fa6 100644
--- a/cargo-insta/Cargo.toml
+++ b/cargo-insta/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cargo-insta"
-version = "1.41.0"
+version = "1.41.1"
 license = "Apache-2.0"
 authors = ["Armin Ronacher <armin.ronacher@active-4.com>"]
 description = "A review tool for the insta snapshot testing library for Rust"
@@ -14,7 +14,12 @@ readme = "README.md"
 rust-version = "1.65.0"
 
 [dependencies]
-insta = { version = "=1.41.0", path = "../insta", features = ["json", "yaml", "redactions", "_cargo_insta_internal"] }
+insta = { version = "=1.41.1", path = "../insta", features = [
+    "json",
+    "yaml",
+    "redactions",
+    "_cargo_insta_internal",
+] }
 cargo_metadata = { version = "0.18.0", default-features = false }
 console = "0.15.4"
 serde = { version = "1.0.117", features = ["derive"] }
@@ -27,15 +32,14 @@ ignore = "0.4.17"
 uuid = { version = "1.0.0", features = ["v4"] }
 tempfile = "3.5.0"
 # Needs pinning in Cargo.lock because of MSRV
-semver = {version = "1.0.7", features = ["serde"]}
-lazy_static = "1.4.0"
-clap = { workspace=true }
+semver = { version = "1.0.7", features = ["serde"] }
+clap = { workspace = true }
 open = "5.3.0"
 itertools = "0.10.0"
 
 [dev-dependencies]
 walkdir = "2.3.1"
-similar= "2.2.1"
+similar = "2.2.1"
 itertools = "0.10.0"
 termcolor = "1.1.2"
-os_pipe = "0.9.0"
+os_pipe = "1.2.1"
diff --git a/cargo-insta/src/cli.rs b/cargo-insta/src/cli.rs
index dd42ae0..c91bf39 100644
--- a/cargo-insta/src/cli.rs
+++ b/cargo-insta/src/cli.rs
@@ -7,7 +7,8 @@ use std::{io, process};
 
 use console::{set_colors_enabled, style, Key, Term};
 use insta::_cargo_insta_support::{
-    is_ci, SnapshotPrinter, SnapshotUpdate, TestRunner, ToolConfig, UnreferencedSnapshots,
+    get_cargo, is_ci, SnapshotPrinter, SnapshotUpdate, TestRunner, ToolConfig,
+    UnreferencedSnapshots,
 };
 use insta::{internals::SnapshotContents, Snapshot};
 use itertools::Itertools;
@@ -28,9 +29,6 @@ use clap::{Args, Parser, Subcommand, ValueEnum};
 #[command(
     bin_name = "cargo insta",
     arg_required_else_help = true,
-    // TODO: do we want these?
-    disable_colored_help = true,
-    disable_version_flag = true,
     next_line_help = true
 )]
 struct Opts {
@@ -61,6 +59,7 @@ impl fmt::Display for ColorWhen {
 
 #[derive(Subcommand, Debug)]
 #[command(
+    version,
     after_help = "For the online documentation of the latest version, see https://insta.rs/docs/cli/."
 )]
 #[allow(clippy::large_enum_variant)]
@@ -191,8 +190,8 @@ struct TestCommand {
     /// Accept all new (previously unseen).
     #[arg(long)]
     accept_unseen: bool,
-    /// Do not reject pending snapshots before run.
-    #[arg(long)]
+    /// Do not reject pending snapshots before run (deprecated).
+    #[arg(long, hide = true)]
     keep_pending: bool,
     /// Update all snapshots even if they are still matching; implies `--accept`.
     #[arg(long)]
@@ -395,8 +394,8 @@ struct LocationInfo<'a> {
     packages: Vec<Package>,
     exts: Vec<&'a str>,
     find_flags: FindFlags,
-    /// The insta version in the current workspace (i.e. not the `cargo-insta`
-    /// binary that's running).
+    /// The tested crate's insta version (i.e. not the `cargo-insta` binary
+    /// that's running this code).
     insta_version: Version,
 }
 
@@ -414,9 +413,6 @@ fn handle_target_args<'a>(
 ) -> Result<LocationInfo<'a>, Box<dyn Error>> {
     let mut cmd = cargo_metadata::MetadataCommand::new();
 
-    // if a workspace root is provided we first check if it points to a
-    // `Cargo.toml`.  If it does we instead treat it as manifest path.  If both
-    // are provided we fail with an error.
     match (
         target_args.workspace_root.as_deref(),
         target_args.manifest_path.as_deref(),
@@ -430,19 +426,17 @@ fn handle_target_args<'a>(
             cmd.manifest_path(manifest);
         }
         (Some(root), None) => {
-            // TODO: should we do this ourselves? Probably fine, but are we
-            // adding anything by not just deferring to cargo?
-            let assumed_manifest = root.join("Cargo.toml");
-            if assumed_manifest.is_file() {
-                cmd.manifest_path(assumed_manifest);
-            } else {
-                cmd.current_dir(root);
-            }
+            cmd.current_dir(root);
         }
         (None, None) => {}
     };
 
-    let metadata = cmd.exec()?;
+    let metadata = cmd.exec().map_err(|e| {
+        format!(
+            "failed to load cargo metadata: {}. Command details: {:?}",
+            e, cmd
+        )
+    })?;
     let workspace_root = metadata.workspace_root.as_std_path().to_path_buf();
     let tool_config = ToolConfig::from_workspace(&workspace_root)?;
 
@@ -642,6 +636,9 @@ fn process_snapshots(
 /// Run the tests
 fn test_run(mut cmd: TestCommand, color: ColorWhen) -> Result<(), Box<dyn Error>> {
     let loc = handle_target_args(&cmd.target_args, &cmd.test_runner_options.package)?;
+
+    // Based on any configs in the config file, update the test command. Default
+    // is `SnapshotUpdate::Auto`.
     match loc.tool_config.snapshot_update() {
         SnapshotUpdate::Auto => {
             if is_ci() {
@@ -677,6 +674,12 @@ fn test_run(mut cmd: TestCommand, color: ColorWhen) -> Result<(), Box<dyn Error>
             style("warning").bold().yellow()
         )
     }
+    if cmd.keep_pending {
+        eprintln!(
+            "{}: `--keep-pending` is deprecated; its behavior is implied: pending snapshots are never removed before a test run.",
+            style("warning").bold().yellow()
+        )
+    }
 
     // the tool config can also indicate that --accept-unseen should be picked
     // automatically unless instructed otherwise.
@@ -699,25 +702,13 @@ fn test_run(mut cmd: TestCommand, color: ColorWhen) -> Result<(), Box<dyn Error>
         TestRunner::CargoTest => TestRunner::CargoTest,
         TestRunner::Nextest => TestRunner::Nextest,
     };
-    // Prioritize the command line over the tool config
-    let test_runner_fallback = cmd
-        .test_runner_fallback
-        .unwrap_or(loc.tool_config.test_runner_fallback());
-
-    let (mut proc, snapshot_ref_file, prevents_doc_run) = prepare_test_runner(
-        test_runner,
-        test_runner_fallback,
-        cmd.unreferenced,
-        &cmd,
-        color,
-        &[],
-        None,
-        &loc,
-    )?;
-
-    if !cmd.keep_pending {
-        process_snapshots(true, None, &loc, Some(Operation::Reject))?;
-    }
+    let test_runner = test_runner.resolve_fallback(
+        cmd.test_runner_fallback
+            .unwrap_or(loc.tool_config.test_runner_fallback()),
+    );
+
+    let (mut proc, snapshot_ref_file, prevents_doc_run) =
+        prepare_test_runner(&cmd, test_runner, color, &[], None, &loc)?;
 
     if let Some(workspace_root) = &cmd.target_args.workspace_root {
         proc.current_dir(workspace_root);
@@ -727,17 +718,16 @@ fn test_run(mut cmd: TestCommand, color: ColorWhen) -> Result<(), Box<dyn Error>
     let status = proc.status()?;
     let mut success = status.success();
 
-    // nextest currently cannot run doctests, run them with regular tests.
+    // nextest currently cannot run doctests, run them with regular tests. We'd
+    // like to deprecate this; see discussion at https://github.com/mitsuhiko/insta/pull/438
     //
     // Note that unlike `cargo test`, `cargo test --doctest` will run doctests
     // even on crates that specify `doctests = false`. But I don't think there's
     // a way to replicate the `cargo test` behavior.
     if matches!(cmd.test_runner, TestRunner::Nextest) && !prevents_doc_run {
         let (mut proc, _, _) = prepare_test_runner(
-            TestRunner::CargoTest,
-            false,
-            cmd.unreferenced,
             &cmd,
+            &TestRunner::CargoTest,
             color,
             &["--doc"],
             snapshot_ref_file.as_deref(),
@@ -827,30 +817,29 @@ fn handle_unreferenced_snapshots(
         UnreferencedSnapshots::Ignore => return Ok(()),
     };
 
-    let mut files = HashSet::new();
-    match fs::read_to_string(snapshot_ref_path) {
-        Ok(s) => {
-            for line in s.lines() {
-                if let Ok(path) = fs::canonicalize(line) {
-                    files.insert(path);
-                }
-            }
-        }
-        Err(err) => {
-            // if the file was not created, no test referenced
-            // snapshots.
-            if err.kind() != io::ErrorKind::NotFound {
-                return Err(err.into());
+    let files = fs::read_to_string(snapshot_ref_path)
+        .map(|s| {
+            s.lines()
+                .filter_map(|line| fs::canonicalize(line).ok())
+                .collect()
+        })
+        .or_else(|err| {
+            if err.kind() == io::ErrorKind::NotFound {
+                // if the file was not created, no test referenced
+                // snapshots (though we also check for this in the calling
+                // function, so maybe duplicative...)
+                Ok(HashSet::new())
+            } else {
+                Err(err)
             }
-        }
-    }
+        })?;
 
     let mut encountered_any = false;
 
     for package in loc.packages.clone() {
         let unreferenced_snapshots = make_snapshot_walker(
             package.manifest_path.parent().unwrap().as_std_path(),
-            &["snap"],
+            &loc.exts,
             FindFlags {
                 include_ignored: true,
                 include_hidden: true,
@@ -858,18 +847,23 @@ fn handle_unreferenced_snapshots(
         )
         .filter_map(|e| e.ok())
         .filter(|e| e.file_type().map(|ft| ft.is_file()).unwrap_or(false))
-        .filter(|e| {
-            e.file_name()
-                .to_str()
-                .map(|name| {
-                    loc.exts
-                        .iter()
-                        .any(|ext| name.ends_with(&format!(".{}", ext)))
-                })
-                .unwrap_or(false)
-        })
         .filter_map(|e| e.path().canonicalize().ok())
-        .filter(|path| !files.contains(path));
+        // The path isn't in the list which the tests wrote to, so it's
+        // unreferenced.
+        //
+        // TODO: note that this will include _all_ `.pending-snap` files,
+        // regardless of whether or not a test was run, since we don't record
+        // those in the snapshot references file. We can make that change, but
+        // also we'd like to unify file & inline snapshot handling; if we do
+        // that it'll fix this smaller issue too.
+        .filter(|path| {
+            // We also check for the pending path
+            let pending_path = path.with_file_name(format!(
+                "{}.new",
+                path.file_name().unwrap().to_string_lossy()
+            ));
+            !files.contains(path) && !files.contains(&pending_path)
+        });
 
         for path in unreferenced_snapshots {
             if !encountered_any {
@@ -888,19 +882,27 @@ fn handle_unreferenced_snapshots(
             }
             eprintln!("  {}", path.display());
             if matches!(action, Action::Delete) {
-                let snapshot = match Snapshot::from_file(&path) {
-                    Ok(snapshot) => snapshot,
-                    Err(e) => {
-                        eprintln!("Error loading snapshot at {:?}: {}", &path, e);
-                        continue;
+                // If it's an inline pending snapshot, then don't attempt to
+                // load it, since these are in a different format; just delete
+                if path.extension() == Some(std::ffi::OsStr::new("pending-snap")) {
+                    if let Err(e) = fs::remove_file(path) {
+                        eprintln!("Failed to remove file: {}", e);
                     }
-                };
+                } else {
+                    let snapshot = match Snapshot::from_file(&path) {
+                        Ok(snapshot) => snapshot,
+                        Err(e) => {
+                            eprintln!("Error loading snapshot at {:?}: {}", &path, e);
+                            continue;
+                        }
+                    };
 
-                if let Some(binary_path) = snapshot.build_binary_path(&path) {
-                    fs::remove_file(&binary_path).ok();
-                }
+                    if let Some(binary_path) = snapshot.build_binary_path(&path) {
+                        fs::remove_file(&binary_path).ok();
+                    }
 
-                fs::remove_file(&path).ok();
+                    fs::remove_file(&path).ok();
+                }
             }
         }
     }
@@ -921,35 +923,14 @@ fn handle_unreferenced_snapshots(
 #[allow(clippy::type_complexity)]
 #[allow(clippy::too_many_arguments)]
 fn prepare_test_runner<'snapshot_ref>(
-    test_runner: TestRunner,
-    test_runner_fallback: bool,
-    unreferenced: UnreferencedSnapshots,
     cmd: &TestCommand,
+    test_runner: &TestRunner,
     color: ColorWhen,
     extra_args: &[&str],
     snapshot_ref_file: Option<&'snapshot_ref Path>,
     loc: &LocationInfo,
 ) -> Result<(process::Command, Option<Cow<'snapshot_ref, Path>>, bool), Box<dyn Error>> {
-    let cargo = env::var_os("CARGO");
-    let cargo = cargo
-        .as_deref()
-        .unwrap_or_else(|| std::ffi::OsStr::new("cargo"));
-    // Fall back to `cargo test` if `cargo nextest` isn't installed and
-    // `test_runner_fallback` is true
-    let test_runner = if test_runner == TestRunner::Nextest
-        && test_runner_fallback
-        && std::process::Command::new(cargo)
-            .arg("nextest")
-            .arg("--version")
-            .output()
-            .map(|output| !output.status.success())
-            .unwrap_or(true)
-    {
-        TestRunner::Auto
-    } else {
-        test_runner
-    };
-    let mut proc = process::Command::new(cargo);
+    let mut proc = process::Command::new(get_cargo());
     match test_runner {
         TestRunner::CargoTest | TestRunner::Auto => {
             proc.arg("test");
@@ -964,7 +945,7 @@ fn prepare_test_runner<'snapshot_ref>(
     proc.env("INSTA_CARGO_INSTA", "1");
     proc.env("INSTA_CARGO_INSTA_VERSION", cargo_insta_version());
 
-    let snapshot_ref_file = if unreferenced != UnreferencedSnapshots::Ignore {
+    let snapshot_ref_file = if cmd.unreferenced != UnreferencedSnapshots::Ignore {
         match snapshot_ref_file {
             Some(path) => Some(Cow::Borrowed(path)),
             None => {
diff --git a/cargo-insta/src/utils.rs b/cargo-insta/src/utils.rs
index dc252b5..94d25d7 100644
--- a/cargo-insta/src/utils.rs
+++ b/cargo-insta/src/utils.rs
@@ -1,5 +1,5 @@
-use std::error::Error;
 use std::fmt;
+use std::{env, error::Error};
 
 /// Close without message but exit code.
 #[derive(Debug)]
diff --git a/insta/Cargo.toml b/insta/Cargo.toml
index 6759e9a..e18e11a 100644
--- a/insta/Cargo.toml
+++ b/insta/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "insta"
-version = "1.41.0"
+version = "1.41.1"
 license = "Apache-2.0"
 authors = ["Armin Ronacher <armin.ronacher@active-4.com>"]
 description = "A snapshot testing library for Rust"
@@ -11,9 +11,7 @@ repository = "https://github.com/mitsuhiko/insta"
 keywords = ["snapshot", "testing", "jest", "approval"]
 categories = ["development-tools::testing"]
 readme = "README.md"
-exclude = [
-    "assets/*"
-]
+exclude = ["assets/*"]
 
 [package.metadata.docs.rs]
 all-features = true
@@ -55,13 +53,16 @@ toml = { version = "0.5.7", optional = true }
 globset = { version = "0.4.6", optional = true }
 walkdir = { version = "2.3.1", optional = true }
 similar = { version = "2.1.0", features = ["inline"] }
-regex = { version = "1.6.0", default-features = false, optional = true, features = ["std", "unicode"] }
+regex = { version = "1.6.0", default-features = false, optional = true, features = [
+    "std",
+    "unicode",
+] }
 serde = { version = "1.0.117", optional = true }
 linked-hash-map = "0.5.6"
-lazy_static = "1.4.0"
+once_cell = "1.20.2"
 # Not yet supported in our MSRV of 1.60.0
 # clap = { workspace=true, optional = true }
-clap = {version = "4.1", features = ["derive", "env"], optional = true}
+clap = { version = "4.1", features = ["derive", "env"], optional = true }
 
 [dev-dependencies]
 rustc_version = "0.4.0"
diff --git a/insta/src/content/serialization.rs b/insta/src/content/serialization.rs
index e9223e2..cb0e951 100644
--- a/insta/src/content/serialization.rs
+++ b/insta/src/content/serialization.rs
@@ -18,7 +18,7 @@ pub enum Key<'a> {
     Other,
 }
 
-impl<'a> Key<'a> {
+impl Key<'_> {
     /// Needed because [`std::mem::discriminant`] is not [`Ord`]
     fn discriminant(&self) -> usize {
         match self {
@@ -35,9 +35,9 @@ impl<'a> Key<'a> {
     }
 }
 
-impl<'a> Eq for Key<'a> {}
+impl Eq for Key<'_> {}
 
-impl<'a> Ord for Key<'a> {
+impl Ord for Key<'_> {
     fn cmp(&self, other: &Self) -> Ordering {
         let self_discriminant = self.discriminant();
         let other_discriminant = other.discriminant();
@@ -58,7 +58,7 @@ impl<'a> Ord for Key<'a> {
     }
 }
 
-impl<'a> PartialOrd for Key<'a> {
+impl PartialOrd for Key<'_> {
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
         Some(self.cmp(other))
     }
diff --git a/insta/src/env.rs b/insta/src/env.rs
index 327f7b8..49fa7f0 100644
--- a/insta/src/env.rs
+++ b/insta/src/env.rs
@@ -10,10 +10,12 @@ use crate::{
     elog,
 };
 
-lazy_static::lazy_static! {
-    static ref WORKSPACES: Mutex<BTreeMap<String, Arc<PathBuf>>> = Mutex::new(BTreeMap::new());
-    static ref TOOL_CONFIGS: Mutex<BTreeMap<PathBuf, Arc<ToolConfig>>> = Mutex::new(BTreeMap::new());
-}
+use once_cell::sync::Lazy;
+
+static WORKSPACES: Lazy<Mutex<BTreeMap<String, Arc<PathBuf>>>> =
+    Lazy::new(|| Mutex::new(BTreeMap::new()));
+static TOOL_CONFIGS: Lazy<Mutex<BTreeMap<PathBuf, Arc<ToolConfig>>>> =
+    Lazy::new(|| Mutex::new(BTreeMap::new()));
 
 pub fn get_tool_config(workspace_dir: &Path) -> Arc<ToolConfig> {
     TOOL_CONFIGS
@@ -37,6 +39,28 @@ pub enum TestRunner {
     Nextest,
 }
 
+#[cfg(feature = "_cargo_insta_internal")]
+impl TestRunner {
+    /// Fall back to `cargo test` if `cargo nextest` isn't installed and
+    /// `test_runner_fallback` is true
+    pub fn resolve_fallback(&self, test_runner_fallback: bool) -> &TestRunner {
+        use crate::utils::get_cargo;
+        if self == &TestRunner::Nextest
+            && test_runner_fallback
+            && std::process::Command::new(get_cargo())
+                .arg("nextest")
+                .arg("--version")
+                .output()
+                .map(|output| !output.status.success())
+                .unwrap_or(true)
+        {
+            &TestRunner::Auto
+        } else {
+            self
+        }
+    }
+}
+
 /// Controls how information is supposed to be displayed.
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum OutputBehavior {
diff --git a/insta/src/glob.rs b/insta/src/glob.rs
index faec3a8..2b587b8 100644
--- a/insta/src/glob.rs
+++ b/insta/src/glob.rs
@@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
 use std::sync::Mutex;
 
 use globset::{GlobBuilder, GlobMatcher};
+use once_cell::sync::Lazy;
 use walkdir::WalkDir;
 
 use crate::env::get_tool_config;
@@ -15,28 +16,24 @@ pub(crate) struct GlobCollector {
     pub(crate) show_insta_hint: bool,
 }
 
-lazy_static::lazy_static! {
-    /// the glob stack holds failure count and an indication if `cargo insta review`
-    /// should be run.
-    pub(crate) static ref GLOB_STACK: Mutex<Vec<GlobCollector>> = Mutex::default();
-}
-
-lazy_static::lazy_static! {
-    static ref GLOB_FILTER: Vec<GlobMatcher> = {
-        env::var("INSTA_GLOB_FILTER")
-            .unwrap_or_default()
-            .split(';')
-            .filter(|x| !x.is_empty())
-            .filter_map(|filter| {
-                GlobBuilder::new(filter)
-                    .case_insensitive(true)
-                    .build()
-                    .ok()
-                    .map(|x| x.compile_matcher())
-            })
-            .collect()
-    };
-}
+/// the glob stack holds failure count and an indication if `cargo insta review`
+/// should be run.
+pub(crate) static GLOB_STACK: Lazy<Mutex<Vec<GlobCollector>>> = Lazy::new(|| Mutex::default());
+
+static GLOB_FILTER: Lazy<Vec<GlobMatcher>> = Lazy::new(|| {
+    env::var("INSTA_GLOB_FILTER")
+        .unwrap_or_default()
+        .split(';')
+        .filter(|x| !x.is_empty())
+        .filter_map(|filter| {
+            GlobBuilder::new(filter)
+                .case_insensitive(true)
+                .build()
+                .ok()
+                .map(|x| x.compile_matcher())
+        })
+        .collect()
+});
 
 pub fn glob_exec<F: FnMut(&Path)>(workspace_dir: &Path, base: &Path, pattern: &str, mut f: F) {
     // If settings.allow_empty_glob() == true and `base` doesn't exist, skip
diff --git a/insta/src/lib.rs b/insta/src/lib.rs
index 663900a..b143dad 100644
--- a/insta/src/lib.rs
+++ b/insta/src/lib.rs
@@ -68,7 +68,7 @@
 //! directly from `cargo test` and control it via the `INSTA_UPDATE` environment
 //! variable — see [Updating snapshots](#updating-snapshots) for details.
 //!
-//! You can for instance first run the tests and not write and new snapshots, and
+//! You can for instance first run the tests and not write any new snapshots, and
 //! if you like them run the tests again and update them:
 //!
 //! ```text
@@ -331,6 +331,7 @@ pub mod _cargo_insta_support {
         snapshot::PendingInlineSnapshot,
         snapshot::SnapshotContents,
         snapshot::TextSnapshotContents,
+        utils::get_cargo,
         utils::is_ci,
     };
 }
diff --git a/insta/src/redaction.rs b/insta/src/redaction.rs
index 423e7fe..5473549 100644
--- a/insta/src/redaction.rs
+++ b/insta/src/redaction.rs
@@ -26,7 +26,7 @@ impl SelectorParseError {
 #[cfg_attr(docsrs, doc(cfg(feature = "redactions")))]
 pub struct ContentPath<'a>(&'a [PathItem]);
 
-impl<'a> fmt::Display for ContentPath<'a> {
+impl fmt::Display for ContentPath<'_> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         for item in self.0.iter() {
             write!(f, ".")?;
@@ -47,7 +47,7 @@ impl<'a> fmt::Display for ContentPath<'a> {
 }
 
 /// Replaces a value with another one.
-
+///
 /// Represents a redaction.
 #[cfg_attr(docsrs, doc(cfg(feature = "redactions")))]
 pub enum Redaction {
diff --git a/insta/src/runtime.rs b/insta/src/runtime.rs
index 5616657..0a4152b 100644
--- a/insta/src/runtime.rs
+++ b/insta/src/runtime.rs
@@ -23,14 +23,13 @@ use crate::{
     snapshot::TextSnapshotKind,
 };
 
-lazy_static::lazy_static! {
-    static ref TEST_NAME_COUNTERS: Mutex<BTreeMap<String, usize>> =
-        Mutex::new(BTreeMap::new());
-    static ref TEST_NAME_CLASH_DETECTION: Mutex<BTreeMap<String, bool>> =
-        Mutex::new(BTreeMap::new());
-    static ref INLINE_DUPLICATES: Mutex<BTreeSet<String>> =
-        Mutex::new(BTreeSet::new());
-}
+use once_cell::sync::Lazy;
+
+static TEST_NAME_COUNTERS: Lazy<Mutex<BTreeMap<String, usize>>> =
+    Lazy::new(|| Mutex::new(BTreeMap::new()));
+static TEST_NAME_CLASH_DETECTION: Lazy<Mutex<BTreeMap<String, bool>>> =
+    Lazy::new(|| Mutex::new(BTreeMap::new()));
+static INLINE_DUPLICATES: Lazy<Mutex<BTreeSet<String>>> = Lazy::new(|| Mutex::new(BTreeSet::new()));
 
 thread_local! {
     static RECORDED_DUPLICATES: RefCell<Vec<BTreeMap<String, Snapshot>>> = RefCell::default()
diff --git a/insta/src/settings.rs b/insta/src/settings.rs
index 1a1f442..6b9bf00 100644
--- a/insta/src/settings.rs
+++ b/insta/src/settings.rs
@@ -1,3 +1,6 @@
+use once_cell::sync::Lazy;
+#[cfg(feature = "serde")]
+use serde::{de::value::Error as ValueError, Serialize};
 use std::cell::RefCell;
 use std::future::Future;
 use std::mem;
@@ -6,9 +9,6 @@ use std::pin::Pin;
 use std::sync::Arc;
 use std::task::{Context, Poll};
 
-#[cfg(feature = "serde")]
-use serde::{de::value::Error as ValueError, Serialize};
-
 use crate::content::Content;
 #[cfg(feature = "serde")]
 use crate::content::ContentSerializer;
@@ -17,26 +17,25 @@ use crate::filters::Filters;
 #[cfg(feature = "redactions")]
 use crate::redaction::{dynamic_redaction, sorted_redaction, ContentPath, Redaction, Selector};
 
-lazy_static::lazy_static! {
-    static ref DEFAULT_SETTINGS: Arc<ActualSettings> = {
-        Arc::new(ActualSettings {
-            sort_maps: false,
-            snapshot_path: "snapshots".into(),
-            snapshot_suffix: "".into(),
-            input_file: None,
-            description: None,
-            info: None,
-            omit_expression: false,
-            prepend_module_to_snapshot: true,
-            #[cfg(feature = "redactions")]
-            redactions: Redactions::default(),
-            #[cfg(feature = "filters")]
-            filters: Filters::default(),
-            #[cfg(feature = "glob")]
-            allow_empty_glob: false,
-        })
-    };
-}
+static DEFAULT_SETTINGS: Lazy<Arc<ActualSettings>> = Lazy::new(|| {
+    Arc::new(ActualSettings {
+        sort_maps: false,
+        snapshot_path: "snapshots".into(),
+        snapshot_suffix: "".into(),
+        input_file: None,
+        description: None,
+        info: None,
+        omit_expression: false,
+        prepend_module_to_snapshot: true,
+        #[cfg(feature = "redactions")]
+        redactions: Redactions::default(),
+        #[cfg(feature = "filters")]
+        filters: Filters::default(),
+        #[cfg(feature = "glob")]
+        allow_empty_glob: false,
+    })
+});
+
 thread_local!(static CURRENT_SETTINGS: RefCell<Settings> = RefCell::new(Settings::new()));
 
 /// Represents stored redactions.
@@ -569,7 +568,7 @@ impl Settings {
         CURRENT_SETTINGS.with(|x| {
             let mut x = x.borrow_mut();
             let old = mem::replace(&mut x.inner, self.inner.clone());
-            SettingsBindDropGuard(Some(old))
+            SettingsBindDropGuard(Some(old), std::marker::PhantomData)
         })
     }
 
@@ -580,8 +579,29 @@ impl Settings {
 }
 
 /// Returned from [`Settings::bind_to_scope`]
+///
+/// This type is not shareable between threads:
+///
+/// ```compile_fail E0277
+/// let mut settings = insta::Settings::clone_current();
+/// settings.set_snapshot_suffix("test drop guard");
+/// let guard = settings.bind_to_scope();
+///
+/// std::thread::spawn(move || { let guard = guard; }); // doesn't compile
+/// ```
+///
+/// This is to ensure tests under async runtimes like `tokio` don't show unexpected results
 #[must_use = "The guard is immediately dropped so binding has no effect. Use `let _guard = ...` to bind it."]
-pub struct SettingsBindDropGuard(Option<Arc<ActualSettings>>);
+pub struct SettingsBindDropGuard(
+    Option<Arc<ActualSettings>>,
+    /// A ZST that is not [`Send`] but is [`Sync`]
+    ///
+    /// This is necessary due to the lack of stable [negative impls](https://github.com/rust-lang/rust/issues/68318).
+    ///
+    /// Required as [`SettingsBindDropGuard`] modifies a thread local variable which would end up
+    /// with unexpected results if sent to a different thread.
+    std::marker::PhantomData<std::sync::MutexGuard<'static, ()>>,
+);
 
 impl Drop for SettingsBindDropGuard {
     fn drop(&mut self) {
diff --git a/insta/src/snapshot.rs b/insta/src/snapshot.rs
index a11ca20..bdc7a8d 100644
--- a/insta/src/snapshot.rs
+++ b/insta/src/snapshot.rs
@@ -1,3 +1,9 @@
+use crate::{
+    content::{self, json, yaml, Content},
+    elog,
+    utils::style,
+};
+use once_cell::sync::Lazy;
 use std::env;
 use std::error::Error;
 use std::fs;
@@ -7,22 +13,14 @@ use std::rc::Rc;
 use std::time::{SystemTime, UNIX_EPOCH};
 use std::{borrow::Cow, fmt};
 
-use crate::{
-    content::{self, json, yaml, Content},
-    elog,
-    utils::style,
-};
-
-lazy_static::lazy_static! {
-    static ref RUN_ID: String = {
-        if let Ok(run_id) = env::var("NEXTEST_RUN_ID") {
-            run_id
-        } else {
-            let d = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
-            format!("{}-{}", d.as_secs(), d.subsec_nanos())
-        }
-    };
-}
+static RUN_ID: Lazy<String> = Lazy::new(|| {
+    if let Ok(run_id) = env::var("NEXTEST_RUN_ID") {
+        run_id
+    } else {
+        let d = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+        format!("{}-{}", d.as_secs(), d.subsec_nanos())
+    }
+});
 
 /// Holds a pending inline snapshot loaded from a json file or read from an assert
 /// macro (doesn't write to the rust file, which is done by `cargo-insta`)
@@ -291,15 +289,13 @@ impl MetaData {
             fields.push(("input_file", Content::from(input_file)));
         }
 
-        let snapshot_type = Content::from(match self.snapshot_kind {
-            SnapshotKind::Text => "text",
+        match self.snapshot_kind {
+            SnapshotKind::Text => {}
             SnapshotKind::Binary { ref extension } => {
                 fields.push(("extension", Content::from(extension.clone())));
-                "binary"
+                fields.push(("snapshot_kind", Content::from("binary")));
             }
-        });
-
-        fields.push(("snapshot_kind", snapshot_type));
+        }
 
         Content::Struct("MetaData", fields)
     }
@@ -385,7 +381,7 @@ impl Snapshot {
                     }
                 }
             }
-            elog!("A snapshot uses a legacy snapshot format; please update it to the new format with `cargo insta test --force-update-snapshots --accept`.\n\nSnapshot is at: {}", p.to_string_lossy());
+            elog!("A snapshot uses a legacy snapshot format; please update it to the new format with `cargo insta test --force-update-snapshots --accept`.\nSnapshot is at: {}", p.to_string_lossy());
             rv
         };
 
@@ -566,6 +562,9 @@ impl Snapshot {
         buf
     }
 
+    // We take `md` as an argument here because the calling methods want to
+    // adjust it; e.g. removing volatile fields when writing to the final
+    // `.snap` file.
     fn save_with_metadata(&self, path: &Path, md: &MetaData) -> Result<(), Box<dyn Error>> {
         if let Some(folder) = path.parent() {
             fs::create_dir_all(folder)?;
@@ -592,9 +591,6 @@ impl Snapshot {
     }
 
     /// Saves the snapshot.
-    ///
-    /// Returns `true` if the snapshot was saved.  This will return `false` if there
-    /// was already a snapshot with matching contents.
     #[doc(hidden)]
     pub fn save(&self, path: &Path) -> Result<(), Box<dyn Error>> {
         self.save_with_metadata(path, &self.metadata.trim_for_persistence())
diff --git a/insta/src/utils.rs b/insta/src/utils.rs
index 35a39da..f800252 100644
--- a/insta/src/utils.rs
+++ b/insta/src/utils.rs
@@ -108,6 +108,15 @@ pub fn format_rust_expression(value: &str) -> Cow<'_, str> {
     Cow::Borrowed(value)
 }
 
+#[cfg(feature = "_cargo_insta_internal")]
+pub fn get_cargo() -> std::ffi::OsString {
+    let cargo = env::var_os("CARGO");
+    let cargo = cargo
+        .as_deref()
+        .unwrap_or_else(|| std::ffi::OsStr::new("cargo"));
+    cargo.to_os_string()
+}
+
 #[test]
 fn test_format_rust_expression() {
     use crate::assert_snapshot;


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 9a95a8f..fd2c71e 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -6,6 +6,7 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR_FORCE: 1
   RUSTDOCFLAGS: "-Dwarnings"
+  RUSTFLAGS: "-D warnings"
 
 jobs:
   test-latest:
diff --git a/cargo-insta/tests/functional/binary.rs b/cargo-insta/tests/functional/binary.rs
index 1f17ad3..e099478 100644
--- a/cargo-insta/tests/functional/binary.rs
+++ b/cargo-insta/tests/functional/binary.rs
@@ -6,21 +6,9 @@ use crate::TestFiles;
 #[test]
 fn test_binary_pending() {
     let test_project = TestFiles::new()
+        .add_cargo_toml("test_binary_pending")
         .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "test_binary_pending"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
-        .add_file(
-            "src/main.rs",
+            "src/lib.rs",
             r#"
 #[test]
 fn test_binary_snapshot() {
@@ -43,7 +31,7 @@ fn test_binary_snapshot() {
     +  Cargo.lock
        Cargo.toml
        src
-         src/main.rs
+         src/lib.rs
     +    src/snapshots
     +      src/snapshots/test_binary_pending__binary_snapshot.snap.new
     +      src/snapshots/test_binary_pending__binary_snapshot.snap.new.txt
@@ -54,21 +42,9 @@ fn test_binary_snapshot() {
 #[test]
 fn test_binary_accept() {
     let test_project = TestFiles::new()
+        .add_cargo_toml("test_binary_accept")
         .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "test_binary_accept"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
-        .add_file(
-            "src/main.rs",
+            "src/lib.rs",
             r#"
 #[test]
 fn test_binary_snapshot() {
@@ -95,7 +71,7 @@ fn test_binary_snapshot() {
     +  Cargo.lock
        Cargo.toml
        src
-         src/main.rs
+         src/lib.rs
     +    src/snapshots
     +      src/snapshots/test_binary_accept__binary_snapshot.snap
     +      src/snapshots/test_binary_accept__binary_snapshot.snap.txt
@@ -108,21 +84,9 @@ fn test_binary_snapshot() {
 #[test]
 fn test_binary_change_extension() {
     let test_project = TestFiles::new()
+        .add_cargo_toml("test_binary_change_extension")
         .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "test_binary_change_extension"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
-        .add_file(
-            "src/main.rs",
+            "src/lib.rs",
             r#"
 #[test]
 fn test_binary_snapshot() {
@@ -142,7 +106,7 @@ fn test_binary_snapshot() {
     assert!(&output.status.success());
 
     test_project.update_file(
-        "src/main.rs",
+        "src/lib.rs",
         r#"
 #[test]
 fn test_binary_snapshot() {
@@ -164,7 +128,7 @@ fn test_binary_snapshot() {
     +  Cargo.lock
        Cargo.toml
        src
-         src/main.rs
+         src/lib.rs
     +    src/snapshots
     +      src/snapshots/test_binary_change_extension__binary_snapshot.snap
     +      src/snapshots/test_binary_change_extension__binary_snapshot.snap.new
@@ -188,7 +152,7 @@ fn test_binary_snapshot() {
     +  Cargo.lock
        Cargo.toml
        src
-         src/main.rs
+         src/lib.rs
     +    src/snapshots
     +      src/snapshots/test_binary_change_extension__binary_snapshot.snap
     +      src/snapshots/test_binary_change_extension__binary_snapshot.snap.json
@@ -200,21 +164,9 @@ fn test_binary_snapshot() {
 #[test]
 fn test_binary_pending_snapshot_removal() {
     let test_project = TestFiles::new()
+        .add_cargo_toml("test_binary_pending_snapshot_removal")
         .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "test_binary_pending_snapshot_removal"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
-        .add_file(
-            "src/main.rs",
+            "src/lib.rs",
             r#"
 #[test]
 fn test_binary_snapshot() {
@@ -225,15 +177,23 @@ fn test_binary_snapshot() {
         )
         .create_project();
 
-    let output = test_project.insta_cmd().args(["test"]).output().unwrap();
-
-    assert!(!&output.status.success());
-
-    test_project.update_file("src/main.rs", "".to_string());
+    assert!(!&test_project
+        .insta_cmd()
+        .args(["test"])
+        .output()
+        .unwrap()
+        .status
+        .success());
 
-    let output = test_project.insta_cmd().args(["test"]).output().unwrap();
+    test_project.update_file("src/lib.rs", "".to_string());
 
-    assert!(&output.status.success());
+    assert!(&test_project
+        .insta_cmd()
+        .args(["test", "--unreferenced=delete"])
+        .output()
+        .unwrap()
+        .status
+        .success());
 
     assert_snapshot!(test_project.file_tree_diff(), @r"
     --- Original file tree
@@ -243,7 +203,7 @@ fn test_binary_snapshot() {
     +  Cargo.lock
        Cargo.toml
        src
-         src/main.rs
+         src/lib.rs
     +    src/snapshots
     ");
 }
@@ -253,21 +213,9 @@ fn test_binary_snapshot() {
 #[test]
 fn test_change_text_to_binary() {
     let test_project = TestFiles::new()
+        .add_cargo_toml("test_change_text_to_binary")
         .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "test_change_text_to_binary"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
-        .add_file(
-            "src/main.rs",
+            "src/lib.rs",
             r#"
 #[test]
 fn test() {
@@ -293,13 +241,13 @@ fn test() {
     +  Cargo.lock
        Cargo.toml
        src
-         src/main.rs
+         src/lib.rs
     +    src/snapshots
     +      src/snapshots/test_change_text_to_binary__test.snap
     ");
 
     test_project.update_file(
-        "src/main.rs",
+        "src/lib.rs",
         r#"
 #[test]
 fn test() {
@@ -324,7 +272,7 @@ fn test() {
     +  Cargo.lock
        Cargo.toml
        src
-         src/main.rs
+         src/lib.rs
     +    src/snapshots
     +      src/snapshots/test_change_text_to_binary__test.snap
     +      src/snapshots/test_change_text_to_binary__test.snap.txt
@@ -336,21 +284,9 @@ fn test() {
 #[test]
 fn test_change_binary_to_text() {
     let test_project = TestFiles::new()
+        .add_cargo_toml("test_change_binary_to_text")
         .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "test_change_binary_to_text"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
-        .add_file(
-            "src/main.rs",
+            "src/lib.rs",
             r#"
 #[test]
 fn test() {
@@ -376,14 +312,14 @@ fn test() {
     +  Cargo.lock
        Cargo.toml
        src
-         src/main.rs
+         src/lib.rs
     +    src/snapshots
     +      src/snapshots/test_change_binary_to_text__some_name.snap
     +      src/snapshots/test_change_binary_to_text__some_name.snap.json
     ");
 
     test_project.update_file(
-        "src/main.rs",
+        "src/lib.rs",
         r#"
 #[test]
 fn test() {
@@ -408,7 +344,7 @@ fn test() {
     +  Cargo.lock
        Cargo.toml
        src
-         src/main.rs
+         src/lib.rs
     +    src/snapshots
     +      src/snapshots/test_change_binary_to_text__some_name.snap
     ");
@@ -417,22 +353,7 @@ fn test() {
 #[test]
 fn test_binary_unreferenced_delete() {
     let test_project = TestFiles::new()
-        .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "test_binary_unreferenced_delete"
-version = "0.1.0"
-edition = "2021"
-
-[lib]
-doctest = false
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
+        .add_cargo_toml("test_binary_unreferenced_delete")
         .add_file(
             "src/lib.rs",
             r#"
diff --git a/cargo-insta/tests/functional/delete_pending.rs b/cargo-insta/tests/functional/delete_pending.rs
new file mode 100644
index 0000000..34a4d95
--- /dev/null
+++ b/cargo-insta/tests/functional/delete_pending.rs
@@ -0,0 +1,253 @@
+use insta::assert_snapshot;
+
+use crate::TestFiles;
+
+/// `--unreferenced=delete` should delete pending snapshots
+#[test]
+fn delete_unreferenced() {
+    let test_project = TestFiles::new()
+        .add_cargo_toml("delete_unreferenced")
+        .add_file(
+            "src/lib.rs",
+            r#"
+#[test]
+fn test_snapshot() {
+    insta::assert_snapshot!("Hello, inline!", @"Hello!");
+}
+
+#[test]
+fn test_snapshot_file() {
+    insta::assert_snapshot!("Hello, world!");
+}
+"#
+            .to_string(),
+        )
+        .create_project();
+
+    assert!(!&test_project
+        .insta_cmd()
+        .args(["test", "--", "--nocapture"])
+        .output()
+        .unwrap()
+        .status
+        .success());
+
+    assert_snapshot!(test_project.file_tree_diff(), @r"
+    --- Original file tree
+    +++ Updated file tree
+    @@ -1,4 +1,8 @@
+     
+    +  Cargo.lock
+       Cargo.toml
+       src
+    +    src/.lib.rs.pending-snap
+         src/lib.rs
+    +    src/snapshots
+    +      src/snapshots/delete_unreferenced__snapshot_file.snap.new
+    ");
+
+    // Now remove the tests; the pending snapshots should be deleted when
+    // passing `--unreferenced=delete`
+    test_project.update_file("src/lib.rs", "".to_string());
+
+    assert!(&test_project
+        .insta_cmd()
+        .args(["test", "--unreferenced=delete", "--", "--nocapture"])
+        .output()
+        .unwrap()
+        .status
+        .success());
+
+    assert_snapshot!(test_project.file_tree_diff(), @r"
+    --- Original file tree
+    +++ Updated file tree
+    @@ -1,4 +1,6 @@
+     
+    +  Cargo.lock
+       Cargo.toml
+       src
+         src/lib.rs
+    +    src/snapshots
+    ");
+}
+
+#[test]
+fn test_pending_snapshot_deletion() {
+    let test_project = TestFiles::new()
+        .add_cargo_toml("test_combined_snapshot_deletion")
+        .add_file(
+            "src/lib.rs",
+            r#"
+use insta::assert_snapshot;
+
+#[test]
+fn test_inline_snapshot() {
+    insta::assert_snapshot!("Hello, world!", @"Hello!");
+}
+
+#[test]
+fn test_file_snapshot() {
+    assert_snapshot!("hello world");
+}
+"#
+            .to_string(),
+        )
+        .create_project();
+
+    // Run the test with `--accept` to create correct snapshots
+    assert!(test_project
+        .insta_cmd()
+        .args(["test", "--accept", "--", "--nocapture"])
+        .output()
+        .unwrap()
+        .status
+        .success());
+
+    // Verify the file tree has a `.snap` file
+    assert_snapshot!(test_project.file_tree_diff(), @r"
+    --- Original file tree
+    +++ Updated file tree
+    @@ -1,4 +1,7 @@
+     
+    +  Cargo.lock
+       Cargo.toml
+       src
+         src/lib.rs
+    +    src/snapshots
+    +      src/snapshots/test_combined_snapshot_deletion__file_snapshot.snap
+    ");
+
+    // Modify the tests to make them fail
+    test_project.update_file(
+        "src/lib.rs",
+        r#"
+use insta::assert_snapshot;
+
+#[test]
+fn test_inline_snapshot() {
+    insta::assert_snapshot!("Hello WORLD!", @"Hello, world!");
+}
+
+#[test]
+fn test_file_snapshot() {
+    assert_snapshot!("hello WORLD");
+}
+"#
+        .to_string(),
+    );
+
+    // Run the tests to create pending snapshots
+    assert!(!test_project
+        .insta_cmd()
+        .args(["test"])
+        .output()
+        .unwrap()
+        .status
+        .success());
+
+    // Verify pending snapshots exist
+    assert_snapshot!(test_project.file_tree_diff(), @r"
+    --- Original file tree
+    +++ Updated file tree
+    @@ -1,4 +1,9 @@
+     
+    +  Cargo.lock
+       Cargo.toml
+       src
+    +    src/.lib.rs.pending-snap
+         src/lib.rs
+    +    src/snapshots
+    +      src/snapshots/test_combined_snapshot_deletion__file_snapshot.snap
+    +      src/snapshots/test_combined_snapshot_deletion__file_snapshot.snap.new
+    ");
+
+    // Run `cargo insta reject` to delete pending snapshots
+    assert!(test_project
+        .insta_cmd()
+        .args(["reject"])
+        .output()
+        .unwrap()
+        .status
+        .success());
+
+    // Pending snapshots should be deleted
+    assert_snapshot!(test_project.file_tree_diff(), @r"
+    --- Original file tree
+    +++ Updated file tree
+    @@ -1,4 +1,7 @@
+     
+    +  Cargo.lock
+       Cargo.toml
+       src
+         src/lib.rs
+    +    src/snapshots
+    +      src/snapshots/test_combined_snapshot_deletion__file_snapshot.snap
+    ");
+
+    // Run the tests again to create pending snapshots
+    assert!(!test_project
+        .insta_cmd()
+        .args(["test"])
+        .output()
+        .unwrap()
+        .status
+        .success());
+
+    // They should be back...
+    assert_snapshot!(test_project.file_tree_diff(), @r"
+    --- Original file tree
+    +++ Updated file tree
+    @@ -1,4 +1,9 @@
+     
+    +  Cargo.lock
+       Cargo.toml
+       src
+    +    src/.lib.rs.pending-snap
+         src/lib.rs
+    +    src/snapshots
+    +      src/snapshots/test_combined_snapshot_deletion__file_snapshot.snap
+    +      src/snapshots/test_combined_snapshot_deletion__file_snapshot.snap.new
+    ");
+
+    // Modify the test back so they pass
+    test_project.update_file(
+        "src/lib.rs",
+        r#"
+use insta::assert_snapshot;
+
+#[test]
+fn test_inline_snapshot() {
+    insta::assert_snapshot!("Hello, world!", @"Hello, world!");
+}
+
+#[test]
+fn test_file_snapshot() {
+    assert_snapshot!("hello world");
+}
+"#
+        .to_string(),
+    );
+
+    // Run the tests with `--unreferenced=delete` to delete pending snapshots
+    assert!(test_project
+        .insta_cmd()
+        .args(["test", "--unreferenced=delete"])
+        .output()
+        .unwrap()
+        .status
+        .success());
+
+    // Verify the pending snapshots are deleted
+    assert_snapshot!(test_project.file_tree_diff(), @r"
+    --- Original file tree
+    +++ Updated file tree
+    @@ -1,4 +1,7 @@
+     
+    +  Cargo.lock
+       Cargo.toml
+       src
+         src/lib.rs
+    +    src/snapshots
+    +      src/snapshots/test_combined_snapshot_deletion__file_snapshot.snap
+    ");
+}
diff --git a/cargo-insta/tests/functional/inline.rs b/cargo-insta/tests/functional/inline.rs
index 4d5aaaa..8a58679 100644
--- a/cargo-insta/tests/functional/inline.rs
+++ b/cargo-insta/tests/functional/inline.rs
@@ -20,10 +20,11 @@ serde = { version = "1.0", features = ["derive"] }
             .to_string(),
         )
         .add_file(
-            "src/main.rs",
+            "src/lib.rs",
             r#"
 use serde::Serialize;
 
+#[allow(dead_code)]
 #[derive(Serialize)]
 struct User {
     id: u64,
@@ -53,10 +54,10 @@ fn test_json_snapshot() {
 
     assert!(&output.status.success());
 
-    assert_snapshot!(test_project.diff("src/main.rs"), @r##"
-    --- Original: src/main.rs
-    +++ Updated: src/main.rs
-    @@ -15,5 +15,10 @@
+    assert_snapshot!(test_project.diff("src/lib.rs"), @r##"
+    --- Original: src/lib.rs
+    +++ Updated: src/lib.rs
+    @@ -16,5 +16,10 @@
          };
          insta::assert_json_snapshot!(&user, {
              ".id" => "[user_id]",
@@ -89,10 +90,11 @@ serde = { version = "1.0", features = ["derive"] }
             .to_string(),
         )
         .add_file(
-            "src/main.rs",
+            "src/lib.rs",
             r#"
 use serde::Serialize;
 
+#[allow(dead_code)]
 #[derive(Serialize)]
 struct User {
     id: u64,
@@ -122,10 +124,10 @@ fn test_yaml_snapshot() {
 
     assert!(&output.status.success());
 
-    assert_snapshot!(test_project.diff("src/main.rs"), @r###"
-    --- Original: src/main.rs
-    +++ Updated: src/main.rs
-    @@ -15,5 +15,8 @@
+    assert_snapshot!(test_project.diff("src/lib.rs"), @r##"
+    --- Original: src/lib.rs
+    +++ Updated: src/lib.rs
+    @@ -16,5 +16,8 @@
          };
          insta::assert_yaml_snapshot!(&user, {
              ".id" => "[user_id]",
@@ -135,27 +137,15 @@ fn test_yaml_snapshot() {
     +    email: john.doe@example.com
     +    "#);
      }
-    "###);
+    "##);
 }
 
 #[test]
 fn test_utf8_inline() {
     let test_project = TestFiles::new()
+        .add_cargo_toml("test_utf8_inline")
         .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "test_utf8_inline"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
-        .add_file(
-            "src/main.rs",
+            "src/lib.rs",
             r#"
 #[test]
 fn test_non_basic_plane() {
@@ -197,9 +187,9 @@ fn test_trailing_comma_in_inline_snapshot() {
 
     assert!(&output.status.success());
 
-    assert_snapshot!(test_project.diff("src/main.rs"), @r##"
-    --- Original: src/main.rs
-    +++ Updated: src/main.rs
+    assert_snapshot!(test_project.diff("src/lib.rs"), @r##"
+    --- Original: src/lib.rs
+    +++ Updated: src/lib.rs
     @@ -1,21 +1,19 @@
      
      #[test]
@@ -310,21 +300,9 @@ fn test_old_yaml_format() {
 #[test]
 fn test_hashtag_escape_in_inline_snapshot() {
     let test_project = TestFiles::new()
+        .add_cargo_toml("test_hashtag_escape")
         .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "test_hashtag_escape"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
-        .add_file(
-            "src/main.rs",
+            "src/lib.rs",
             r#####"
 #[test]
 fn test_hashtag_escape() {
@@ -344,9 +322,9 @@ fn test_hashtag_escape() {
 
     assert!(&output.status.success());
 
-    assert_snapshot!(test_project.diff("src/main.rs"), @r####"
-    --- Original: src/main.rs
-    +++ Updated: src/main.rs
+    assert_snapshot!(test_project.diff("src/lib.rs"), @r####"
+    --- Original: src/lib.rs
+    +++ Updated: src/lib.rs
     @@ -2,5 +2,8 @@
      #[test]
      fn test_hashtag_escape() {
diff --git a/cargo-insta/tests/functional/main.rs b/cargo-insta/tests/functional/main.rs
index d92cd86..ccb4320 100644
--- a/cargo-insta/tests/functional/main.rs
+++ b/cargo-insta/tests/functional/main.rs
@@ -60,6 +60,7 @@ use similar::udiff::unified_diff;
 use tempfile::TempDir;
 
 mod binary;
+mod delete_pending;
 mod inline;
 mod workspace;
 
@@ -106,6 +107,29 @@ impl TestFiles {
         self
     }
 
+    /// Adds a standard `Cargo.toml` (some tests may need to add_file themselves
+    /// with a different format)
+    fn add_cargo_toml(self, name: &str) -> Self {
+        self.add_file(
+            "Cargo.toml",
+            format!(
+                r#"
+[package]
+name = "{}"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+doctest = false
+
+[dependencies]
+insta = {{ path = '$PROJECT_PATH' }}
+"#,
+                name
+            ),
+        )
+    }
+
     fn create_project(self) -> TestProject {
         TestProject::new(self.files)
     }
@@ -203,7 +227,7 @@ impl TestProject {
                 format!("{} {}", style(&stdout_name).green(), line)
             }))
             .stderr(OutputFormatter(move |line| {
-                format!("{} {}", style(&stderr_name).red(), line)
+                format!("{} {}", style(&stderr_name).yellow(), line)
             }));
 
         command
@@ -228,6 +252,7 @@ impl TestProject {
 
     fn current_file_tree(workspace_dir: &Path) -> String {
         WalkBuilder::new(workspace_dir)
+            .hidden(false)
             .filter_entry(|e| e.path().file_name() != Some(std::ffi::OsStr::new("target")))
             .build()
             .filter_map(|e| e.ok())
@@ -314,34 +339,33 @@ Hello, world!
     let test_insta_1_40_0 = create_test_force_update_project("1_40_0", "\"1.40.0\"");
 
     // Test with current insta version
-    let output_current = test_current_insta
+    assert!(&test_current_insta
         .insta_cmd()
         .args(["test", "--accept", "--force-update-snapshots"])
         .output()
-        .unwrap();
-
-    assert!(&output_current.status.success());
+        .unwrap()
+        .status
+        .success());
 
     // Test with insta 1.40.0
-    let output_1_40_0 = test_insta_1_40_0
+    assert!(&test_insta_1_40_0
         .insta_cmd()
         .args(["test", "--accept", "--force-update-snapshots"])
         .output()
-        .unwrap();
-
-    assert!(&output_1_40_0.status.success());
+        .unwrap()
+        .status
+        .success());
 
     // Check that both versions updated the snapshot correctly
     assert_snapshot!(test_current_insta.diff("src/snapshots/test_force_update_current__force_update.snap"), @r#"
     --- Original: src/snapshots/test_force_update_current__force_update.snap
     +++ Updated: src/snapshots/test_force_update_current__force_update.snap
-    @@ -1,8 +1,6 @@
+    @@ -1,8 +1,5 @@
     -
      ---
      source: src/lib.rs
     -expression: 
     +expression: "\"Hello, world!\""
-    +snapshot_kind: text
      ---
      Hello, world!
     -
@@ -368,19 +392,7 @@ Hello, world!
 #[test]
 fn test_force_update_inline_snapshot_linebreaks() {
     let test_project = TestFiles::new()
-        .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "force-update-inline-linebreaks"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
+        .add_cargo_toml("force-update-inline-linebreaks")
         .add_file(
             "src/lib.rs",
             r#####"
@@ -425,19 +437,7 @@ fn test_linebreaks() {
 #[test]
 fn test_force_update_inline_snapshot_hashes() {
     let test_project = TestFiles::new()
-        .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "force-update-inline-hashes"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
+        .add_cargo_toml("force-update-inline-hashes")
         .add_file(
             "src/lib.rs",
             r#####"
@@ -531,22 +531,7 @@ fn test_matches_fully_linebreaks() {
     // Until #563 merges, we should be OK with different leading newlines, even
     // in exact / full match mode.
     let test_project = TestFiles::new()
-        .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "exact-match-inline"
-version = "0.1.0"
-edition = "2021"
-
-[lib]
-doctest = false
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
+        .add_cargo_toml("exact-match-inline")
         .add_file(
             "src/lib.rs",
             r#####"
@@ -589,22 +574,7 @@ fn test_additional_linebreak() {
 #[test]
 fn test_snapshot_name_clash() {
     let test_project = TestFiles::new()
-        .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "snapshot_name_clash_test"
-version = "0.1.0"
-edition = "2021"
-
-[lib]
-doctest = false
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
+        .add_cargo_toml("snapshot_name_clash_test")
         .add_file(
             "src/lib.rs",
             r#"
@@ -643,22 +613,7 @@ fn foo_always_missing() {
 #[test]
 fn test_unreferenced_delete() {
     let test_project = TestFiles::new()
-        .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "test_unreferenced_delete"
-version = "0.1.0"
-edition = "2021"
-
-[lib]
-doctest = false
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
+        .add_cargo_toml("test_unreferenced_delete")
         .add_file(
             "src/lib.rs",
             r#"
@@ -743,19 +698,7 @@ Unused snapshot
 #[test]
 fn test_hidden_snapshots() {
     let test_project = TestFiles::new()
-        .add_file(
-            "Cargo.toml",
-            r#"
-[package]
-name = "test_hidden_snapshots"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
-"#
-            .to_string(),
-        )
+        .add_cargo_toml("test_hidden_snapshots")
         .add_file(
             "src/lib.rs",
             r#"
@@ -821,21 +764,66 @@ Hidden snapshot
 }
 
 #[test]
-fn test_ignored_snapshots() {
+fn test_snapshot_kind_behavior() {
     let test_project = TestFiles::new()
+        .add_cargo_toml("test_snapshot_kind")
         .add_file(
-            "Cargo.toml",
+            "src/lib.rs",
             r#"
-[package]
-name = "test_ignored_snapshots"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-insta = { path = '$PROJECT_PATH' }
+#[test]
+fn test_snapshots() {
+    insta::assert_snapshot!("new snapshot");
+    insta::assert_snapshot!("existing snapshot");
+}
+"#
+            .to_string(),
+        )
+        .add_file(
+            "src/snapshots/test_snapshot_kind__existing.snap",
+            r#"---
+source: src/lib.rs
+expression: "\"existing snapshot\""
+snapshot_kind: text
+---
+existing snapshot
 "#
             .to_string(),
         )
+        .create_project();
+
+    // Run the test with --accept to create the new snapshot
+    let output = test_project
+        .insta_cmd()
+        .args(["test", "--accept"])
+        .output()
+        .unwrap();
+
+    assert!(output.status.success());
+
+    // Verify the new snapshot was created without snapshot_kind
+    let new_snapshot = std::fs::read_to_string(
+        test_project
+            .workspace_dir
+            .join("src/snapshots/test_snapshot_kind__snapshots.snap"),
+    )
+    .unwrap();
+
+    assert!(!new_snapshot.contains("snapshot_kind:"));
+
+    // Verify both snapshots work with --require-full-match
+    let output = test_project
+        .insta_cmd()
+        .args(["test", "--require-full-match"])
+        .output()
+        .unwrap();
+
+    assert!(output.status.success());
+}
+
+#[test]
+fn test_ignored_snapshots() {
+    let test_project = TestFiles::new()
+        .add_cargo_toml("test_ignored_snapshots")
         .add_file(
             "src/lib.rs",
             r#"
diff --git a/cargo-insta/tests/functional/workspace.rs b/cargo-insta/tests/functional/workspace.rs
index f70c8d5..b3976fe 100644
--- a/cargo-insta/tests/functional/workspace.rs
+++ b/cargo-insta/tests/functional/workspace.rs
@@ -389,19 +389,7 @@ fn test_insta_workspace_root() {
     }
 
     let test_project = TestFiles::new()
-        .add_file(
-            "Cargo.toml",
-            r#"
-    [package]
-    name = "insta_workspace_root_test"
-    version = "0.1.0"
-    edition = "2021"
-
-    [dependencies]
-    insta = { path = '$PROJECT_PATH' }
-    "#
-            .to_string(),
-        )
+        .add_cargo_toml("insta_workspace_root_test")
         .add_file(
             "src/lib.rs",
             r#"
@@ -588,7 +576,6 @@ fn test_hello() {
     ---
     source: "../tests/lib.rs"
     expression: hello()
-    snapshot_kind: text
     ---
     Hello, world!
     "#);