Repository: mitsuhiko/insta (language: rust)

Issue:
Settings::bind_to_scope()'s drop guard is Send
### What happened?

`Settings::bind_to_scope()`'s drop guard is `Send`, this means you can transfer it to a separate thread and break the settings like that.

This is not normally an issue, but using this in a multi-threaded executor like `tokio` could cause weird failures (when a future changes threads).

### Reproduction steps

1. `let guard = settings.bind_to_scope();`
2. `std::thread::spawn(move || { let _ = guard; })`
3. Observe settings in current thread are not reset.


### Insta Version

1.41.1

### rustc Version

1.83

### What did you expect?

`Settings::bind_to_scope()`'s drop guard should not be `Send` so this is prevented at compile time.


Discussion / hints:
Thanks @jalil-salame . Would you be open to a PR?
> Thanks @jalil-salame . Would you be open to a PR?

Sure! I should have time tomorrow

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6c5f8f1c..8786cb2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
 
 All notable changes to insta and cargo-insta are documented here.
 
+## Unreleased
+
+- `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.
diff --git a/insta/src/settings.rs b/insta/src/settings.rs
index 1a1f4425..716f8ae1 100644
--- a/insta/src/settings.rs
+++ b/insta/src/settings.rs
@@ -569,7 +569,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 +580,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) {


Test patch: