Repository: mitsuhiko/insta
Pull request #49 (2019-08-12 14:09:20)

If tests pass, clear out pending snapshots
This deletes stale pending snapshots after tests as good as possible.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fba68cb..af0c032 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,3 +6,4 @@
 * make RON support optional.
 * added support for sorting maps before serialization.
 * added settings support.
+* added support for overriding the snapshot path.
diff --git a/Cargo.toml b/Cargo.toml
index 4c8f183..abfa3ca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,7 +33,7 @@ serialization = []
 difference = "2.0.0"
 serde = { version = "1.0.85", features = ["derive"] }
 serde_yaml = "0.8.8"
-console = "0.7.5"
+console = "0.8.0"
 chrono = { version = "0.4.6", features = ["serde"] }
 serde_json = "1.0.36"
 lazy_static = "1.2.0"
diff --git a/cargo-insta/src/cargo.rs b/cargo-insta/src/cargo.rs
index 36b0544..45334b1 100644
--- a/cargo-insta/src/cargo.rs
+++ b/cargo-insta/src/cargo.rs
@@ -2,7 +2,7 @@ use std::collections::HashSet;
 use std::env;
 use std::error::Error;
 use std::fs;
-use std::path::{Component, Path, PathBuf};
+use std::path::{Path, PathBuf};
 use std::process;
 
 use insta::{PendingInlineSnapshot, Snapshot};
@@ -121,16 +121,29 @@ impl SnapshotContainer {
                 let mut pending_vec = PendingInlineSnapshot::load_batch(&snapshot_path)?;
                 let mut patcher = FilePatcher::open(&target_path)?;
                 pending_vec.sort_by_key(|pending| pending.line);
+                let mut have_new = false;
                 for (id, pending) in pending_vec.into_iter().enumerate() {
-                    snapshots.push(PendingSnapshot {
-                        id,
-                        old: pending.old,
-                        new: pending.new,
-                        op: Operation::Skip,
-                        line: Some(pending.line),
-                    });
-                    patcher.add_snapshot_macro(pending.line as usize);
+                    if let Some(new) = pending.new {
+                        snapshots.push(PendingSnapshot {
+                            id,
+                            old: pending.old,
+                            new,
+                            op: Operation::Skip,
+                            line: Some(pending.line),
+                        });
+                        patcher.add_snapshot_macro(pending.line as usize);
+                        have_new = true;
+                    }
+                }
+
+                // if we don't actually have any new pending we better delete the file.
+                // this can happen if the test code left a stale snapshot behind.
+                // The runtime code will issue something like this:
+                //   PendingInlineSnapshot::new(None, None, line).save(pending_snapshots)?;
+                if !have_new {
+                    fs::remove_file(&snapshot_path)?;
                 }
+
                 Some(patcher)
             }
         };
@@ -174,7 +187,7 @@ impl SnapshotContainer {
                     Operation::Reject => {}
                     Operation::Skip => {
                         new_pending.push(PendingInlineSnapshot::new(
-                            snapshot.new.clone(),
+                            Some(snapshot.new.clone()),
                             snapshot.old.clone(),
                             patcher.get_new_line(idx) as u32,
                         ));
@@ -229,14 +242,6 @@ pub fn find_snapshots<'a>(
             let fname = e.file_name().to_string_lossy();
             if fname.ends_with(".new")
                 && extensions.contains(&fname.rsplit('.').nth(1).unwrap_or(""))
-                && e.path()
-                    .strip_prefix(&root)
-                    .unwrap()
-                    .components()
-                    .any(|c| match c {
-                        Component::Normal(dir) => dir.to_str() == Some("snapshots"),
-                        _ => false,
-                    })
             {
                 let new_path = e.into_path();
                 let mut old_path = new_path.clone();
diff --git a/src/macros.rs b/src/macros.rs
index 2ce0f27..dca55d4 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -332,8 +332,10 @@ macro_rules! assert_snapshot_matches {
 
 /// Settings configuration macro.
 ///
-/// This macro lets you bind some settings temporarily.  Currently
-/// only `sort_maps` is supported:
+/// This macro lets you bind some settings temporarily.  The first argument
+/// takes key value pairs that should be set, the second is the block to
+/// execute.  All settings can be set (`sort_maps => value` maps roughly
+/// to `set_sort_maps(value)`).
 ///
 /// ```rust,ignore
 /// insta::with_settings!({sort_maps => true}, {
diff --git a/src/runtime.rs b/src/runtime.rs
index 604e2de..8e9bc50 100644
--- a/src/runtime.rs
+++ b/src/runtime.rs
@@ -19,6 +19,7 @@ use lazy_static::lazy_static;
 use serde::Deserialize;
 use serde_json;
 
+use crate::settings::Settings;
 use crate::snapshot::{MetaData, PendingInlineSnapshot, Snapshot, SnapshotContents};
 use crate::utils::is_ci;
 
@@ -269,9 +270,11 @@ pub fn get_snapshot_filename(
 ) -> PathBuf {
     let root = Path::new(cargo_workspace);
     let base = Path::new(base);
-    root.join(base.parent().unwrap())
-        .join("snapshots")
-        .join(format!("{}__{}.snap", module_name, snapshot_name))
+    Settings::with(|settings| {
+        root.join(base.parent().unwrap())
+            .join(settings.snapshot_path())
+            .join(format!("{}__{}.snap", module_name, snapshot_name))
+    })
 }
 
 /// Prints a diff against an old snapshot.
@@ -527,6 +530,19 @@ pub fn assert_snapshot(
     // if the snapshot matches we're done.
     if let Some(ref old_snapshot) = old {
         if old_snapshot.contents() == &new_snapshot_contents {
+            // let's just make sure there are no more pending files lingering
+            // around.
+            if let Some(mut snapshot_file) = snapshot_file {
+                snapshot_file.set_extension("snap.new");
+                fs::remove_file(snapshot_file).ok();
+            }
+            // and add a null pending snapshot to a pending snapshot file if needed
+            if let Some(pending_snapshots) = pending_snapshots {
+                if fs::metadata(&pending_snapshots).is_ok() {
+                    PendingInlineSnapshot::new(None, None, line).save(pending_snapshots)?;
+                }
+            }
+
             return Ok(());
         }
     }
@@ -589,7 +605,8 @@ pub fn assert_snapshot(
                     style(new_path.display()).cyan().underlined(),
                 );
             } else {
-                PendingInlineSnapshot::new(new, old, line).save(pending_snapshots.unwrap())?;
+                PendingInlineSnapshot::new(Some(new), old, line)
+                    .save(pending_snapshots.unwrap())?;
             }
         }
         UpdateBehavior::NoUpdate => {}
diff --git a/src/settings.rs b/src/settings.rs
index 380c152..c6c7e2e 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -1,5 +1,6 @@
 use lazy_static::lazy_static;
 use std::cell::RefCell;
+use std::path::{Path, PathBuf};
 use std::sync::Arc;
 
 #[cfg(feature = "redactions")]
@@ -8,6 +9,7 @@ use crate::{content::Content, redaction::Selector};
 lazy_static! {
     static ref DEFAULT_SETTINGS: Arc<ActualSettings> = Arc::new(ActualSettings {
         sort_maps: false,
+        snapshot_path: "snapshots".into(),
         #[cfg(feature = "redactions")]
         redactions: Redactions::default(),
     });
@@ -35,6 +37,7 @@ impl<'a> From<Vec<(&'a str, Content)>> for Redactions {
 #[doc(hidden)]
 pub struct ActualSettings {
     pub sort_maps: bool,
+    pub snapshot_path: PathBuf,
     #[cfg(feature = "redactions")]
     pub redactions: Redactions,
 }
@@ -91,6 +94,8 @@ impl Settings {
     ///
     /// Note that this only applies to snapshots that undergo serialization
     /// (eg: does not work for `assert_debug_snapshot_matches!`.)
+    ///
+    /// The default value is `false`.
     pub fn set_sort_maps(&mut self, value: bool) {
         self._private_inner_mut().sort_maps = value;
     }
@@ -116,6 +121,8 @@ impl Settings {
     }
 
     /// Replaces the currently set redactions.
+    ///
+    /// The default set is empty.
     #[cfg(feature = "redactions")]
     pub fn set_redactions<R: Into<Redactions>>(&mut self, redactions: R) {
         self._private_inner_mut().redactions = redactions.into();
@@ -133,6 +140,20 @@ impl Settings {
         self.inner.redactions.0.iter().map(|&(ref a, ref b)| (a, b))
     }
 
+    /// Sets the snapshot path.
+    ///
+    /// If not absolute it's relative to where the test is in.
+    ///
+    /// Defaults to `snapshots`.
+    pub fn set_snapshot_path<P: AsRef<Path>>(&mut self, path: P) {
+        self._private_inner_mut().snapshot_path = path.as_ref().to_path_buf();
+    }
+
+    /// Returns the snapshot path.
+    pub fn snapshot_path(&self) -> &Path {
+        &self.inner.snapshot_path
+    }
+
     /// Runs a function with the current settings bound to the thread.
     pub fn bind<F: FnOnce()>(&self, f: F) {
         CURRENT_SETTINGS.with(|x| {
diff --git a/src/snapshot.rs b/src/snapshot.rs
index f7da59c..67315be 100644
--- a/src/snapshot.rs
+++ b/src/snapshot.rs
@@ -19,12 +19,12 @@ lazy_static! {
 pub struct PendingInlineSnapshot {
     pub run_id: Uuid,
     pub line: u32,
-    pub new: Snapshot,
+    pub new: Option<Snapshot>,
     pub old: Option<Snapshot>,
 }
 
 impl PendingInlineSnapshot {
-    pub fn new(new: Snapshot, old: Option<Snapshot>, line: u32) -> PendingInlineSnapshot {
+    pub fn new(new: Option<Snapshot>, old: Option<Snapshot>, line: u32) -> PendingInlineSnapshot {
         PendingInlineSnapshot {
             new,
             old,


Test patch:
diff --git a/tests/snapshots2/test_settings__snapshot_path.snap b/tests/snapshots2/test_settings__snapshot_path.snap
new file mode 100644
index 0000000..a967ea8
--- /dev/null
+++ b/tests/snapshots2/test_settings__snapshot_path.snap
@@ -0,0 +1,9 @@
+---
+created: "2019-08-23T22:20:37.202188Z"
+creator: insta@0.10.1
+source: tests/test_settings.rs
+expression: "vec![1 , 2 , 3]"
+---
+- 1
+- 2
+- 3
diff --git a/tests/test_settings.rs b/tests/test_settings.rs
index 60d3e12..849b01d 100644
--- a/tests/test_settings.rs
+++ b/tests/test_settings.rs
@@ -60,3 +60,10 @@ fn test_settings_macro() {
         "###);
     });
 }
+
+#[test]
+fn test_snapshot_path() {
+    with_settings!({snapshot_path => "snapshots2"}, {
+        assert_yaml_snapshot_matches!(vec![1, 2, 3]);
+    });
+}