Repository: mitsuhiko/insta (Rust)

Issue:
glob!() that matches no files counts as a success
## Description

The glob!() macro doesn't raise any kind of objection if the glob matches no files; it just runs the closure zero times. This seems unlikely to be intended in test code.

## Reproduction steps

```
#[test]
fn glob_fails() {
    insta::glob!("nonexistent", |_| { panic!(); });
}
```

Additional helpful information:

- Version of insta: 1.4.0
- Version of rustc: 1.48.0
- Operating system and version: Debian bullssye

## What did you expect

I would expect an assert!() that the glob matched at least one file, to guard against incorrect globs in tests.




Discussion:
Thanks!



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7c4aefec..fa720232 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Changelog
 
+## 1.5.1
+
+* Fixed glob not working correctly.
+* Fail by default if glob is not returning any matches. Fixes #151.
+
 ## 1.5.0
 
 * Add `pending-snapshots` parameter to `cargo-insta`.
diff --git a/src/glob.rs b/src/glob.rs
index 20e2c264..257a243b 100644
--- a/src/glob.rs
+++ b/src/glob.rs
@@ -14,20 +14,27 @@ pub fn glob_exec<F: FnMut(&Path)>(base: &Path, pattern: &str, mut f: F) {
         .compile_matcher();
 
     let walker = WalkDir::new(base).follow_links(true);
+    let mut glob_found_matches = false;
+    let mut settings = Settings::clone_current();
 
     for file in walker {
         let file = file.unwrap();
         let path = file.path();
-        if !glob.is_match(path) {
+        let stripped_path = path.strip_prefix(base).unwrap_or(path);
+        if !glob.is_match(stripped_path) {
             continue;
         }
 
-        let mut settings = Settings::clone_current();
         settings.set_input_file(&path);
         settings.set_snapshot_suffix(path.file_name().unwrap().to_str().unwrap());
 
+        glob_found_matches = true;
         settings.bind(|| {
             f(path);
         });
     }
+
+    if !glob_found_matches && !settings.allow_empty_glob() {
+        panic!("the glob! macro did not match any files.");
+    }
 }
diff --git a/src/settings.rs b/src/settings.rs
index a868e4e9..031ce256 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -21,6 +21,8 @@ lazy_static! {
         prepend_module_to_snapshot: true,
         #[cfg(feature = "redactions")]
         redactions: Redactions::default(),
+        #[cfg(feature = "glob")]
+        allow_empty_glob: false,
     });
 }
 thread_local!(static CURRENT_SETTINGS: RefCell<Settings> = RefCell::new(Settings::new()));
@@ -52,6 +54,8 @@ pub struct ActualSettings {
     pub prepend_module_to_snapshot: bool,
     #[cfg(feature = "redactions")]
     pub redactions: Redactions,
+    #[cfg(feature = "glob")]
+    pub allow_empty_glob: bool,
 }
 
 /// Configures how insta operates at test time.
@@ -125,7 +129,7 @@ impl Settings {
         self.inner.sort_maps
     }
 
-    /// Disbales prepending of modules to the snapshot filename.
+    /// Disables prepending of modules to the snapshot filename.
     ///
     /// By default the filename of a snapshot is `<module>__<name>.snap`.
     /// Setting this flag to `false` changes the snapshot filename to just
@@ -141,6 +145,24 @@ impl Settings {
         self.inner.prepend_module_to_snapshot
     }
 
+    /// Allows the `glob!` macro to succeed if it matches no files.
+    ///
+    /// By default the glob macro will fail the test if it does not find
+    /// any files to prevent accidental typos.  This can be disabled when
+    /// fixtures should be conditional.
+    ///
+    /// The default value is `false`.
+    #[cfg(feature = "glob")]
+    pub fn set_allow_empty_glob(&mut self, value: bool) {
+        self._private_inner_mut().allow_empty_glob = value;
+    }
+
+    /// Returns the current value for the empty glob setting.
+    #[cfg(feature = "glob")]
+    pub fn allow_empty_glob(&self) -> bool {
+        self.inner.allow_empty_glob
+    }
+
     /// Sets the snapshot suffix.
     ///
     /// The snapshot suffix is added to all snapshot names with an `@` sign


Test patch:
diff --git a/tests/snapshots/test_glob__basic_globbing@goodbye.txt.snap b/tests/snapshots/test_glob__basic_globbing@goodbye.txt.snap
new file mode 100644
index 00000000..a16cfe30
--- /dev/null
+++ b/tests/snapshots/test_glob__basic_globbing@goodbye.txt.snap
@@ -0,0 +1,6 @@
+---
+source: tests/test_glob.rs
+expression: "&contents"
+input_file: tests/inputs/goodbye.txt
+---
+"Contents of goodbye"
diff --git a/tests/snapshots/test_glob__basic_globbing@hello.txt.snap b/tests/snapshots/test_glob__basic_globbing@hello.txt.snap
new file mode 100644
index 00000000..6f941346
--- /dev/null
+++ b/tests/snapshots/test_glob__basic_globbing@hello.txt.snap
@@ -0,0 +1,6 @@
+---
+source: tests/test_glob.rs
+expression: "&contents"
+input_file: tests/inputs/hello.txt
+---
+"Contents of hello"
diff --git a/tests/snapshots/test_glob__globs_follow_links@goodbye.txt.snap b/tests/snapshots/test_glob__globs_follow_links@goodbye.txt.snap
new file mode 100644
index 00000000..a16cfe30
--- /dev/null
+++ b/tests/snapshots/test_glob__globs_follow_links@goodbye.txt.snap
@@ -0,0 +1,6 @@
+---
+source: tests/test_glob.rs
+expression: "&contents"
+input_file: tests/inputs/goodbye.txt
+---
+"Contents of goodbye"
diff --git a/tests/snapshots/test_glob__globs_follow_links@hello.txt.snap b/tests/snapshots/test_glob__globs_follow_links@hello.txt.snap
new file mode 100644
index 00000000..6f941346
--- /dev/null
+++ b/tests/snapshots/test_glob__globs_follow_links@hello.txt.snap
@@ -0,0 +1,6 @@
+---
+source: tests/test_glob.rs
+expression: "&contents"
+input_file: tests/inputs/hello.txt
+---
+"Contents of hello"
diff --git a/tests/test_glob.rs b/tests/test_glob.rs
index 76999581..08c483f1 100644
--- a/tests/test_glob.rs
+++ b/tests/test_glob.rs
@@ -15,3 +15,11 @@ fn test_globs_follow_links() {
         insta::assert_json_snapshot!(&contents);
     });
 }
+
+#[test]
+#[should_panic(expected = "the glob! macro did not match any files.")]
+fn test_empty_glob_fails() {
+    insta::glob!("nonexistent", |_| {
+        // nothing
+    });
+}