Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion insta/src/comparator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ use crate::snapshot::{Snapshot, SnapshotContents, TextSnapshotKind};
///
/// This trait requires `'static` so that implementing structs can be stored in
/// [`crate::settings::Settings`].
pub trait Comparator: 'static {
// TODO: `Send + Sync` is required because `Settings` currently uses `Arc`
// internally. Consider removing these bounds if `Settings` switches to `Rc`
// in the next breaking change.
pub trait Comparator: Send + Sync + 'static {
/// Returns `true` if the contents of `reference` and `test` match.
///
/// This is the standard comparison used by [`assert_snapshot!`].
Expand Down
4 changes: 2 additions & 2 deletions insta/src/redaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub enum Redaction {
/// Static redaction with new content.
Static(Content),
/// Redaction with new content.
Dynamic(Box<dyn Fn(Content, ContentPath<'_>) -> Content>),
Dynamic(Box<dyn Fn(Content, ContentPath<'_>) -> Content + Sync + Send>),
}

macro_rules! impl_from {
Expand Down Expand Up @@ -127,7 +127,7 @@ impl<'a> From<&'a [u8]> for Redaction {
pub fn dynamic_redaction<I, F>(func: F) -> Redaction
where
I: Into<Content>,
F: Fn(Content, ContentPath<'_>) -> I + 'static,
F: Fn(Content, ContentPath<'_>) -> I + Send + Sync + 'static,
{
Redaction::Dynamic(Box::new(move |c, p| func(c, p).into()))
}
Expand Down
34 changes: 24 additions & 10 deletions insta/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::future::Future;
use std::mem;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;
use std::task::{Context, Poll};

use crate::comparator::Comparator;
Expand All @@ -23,15 +23,15 @@ thread_local!(static CURRENT_SETTINGS: RefCell<Settings> = RefCell::new(Settings
#[cfg(feature = "redactions")]
#[cfg_attr(docsrs, doc(cfg(feature = "redactions")))]
#[derive(Clone, Default)]
pub struct Redactions(Vec<(Selector<'static>, Rc<Redaction>)>);
pub struct Redactions(Vec<(Selector<'static>, Arc<Redaction>)>);

#[cfg(feature = "redactions")]
impl<'a> From<Vec<(&'a str, Redaction)>> for Redactions {
fn from(value: Vec<(&'a str, Redaction)>) -> Redactions {
Redactions(
value
.into_iter()
.map(|x| (Selector::parse(x.0).unwrap().make_static(), Rc::new(x.1)))
.map(|x| (Selector::parse(x.0).unwrap().make_static(), Arc::new(x.1)))
.collect(),
)
}
Expand Down Expand Up @@ -188,13 +188,17 @@ impl ActualSettings {
/// ```
#[derive(Clone)]
pub struct Settings {
inner: Rc<ActualSettings>,
// TODO: consider switching to `Rc` in the next breaking change — `Settings`
// are stored in thread-local storage and never cross thread boundaries, so
// `Arc` is unnecessary. Removing it would also let us drop the `Send + Sync`
// bounds on `Redaction` and `Comparator`.
inner: Arc<ActualSettings>,
}

impl Default for Settings {
fn default() -> Settings {
Settings {
inner: Rc::new(ActualSettings {
inner: Arc::new(ActualSettings {
sort_maps: false,
snapshot_path: "snapshots".into(),
snapshot_suffix: "".into(),
Expand Down Expand Up @@ -232,7 +236,7 @@ impl Settings {
/// Internal helper for macros
#[doc(hidden)]
pub fn _private_inner_mut(&mut self) -> &mut ActualSettings {
Rc::make_mut(&mut self.inner)
Arc::make_mut(&mut self.inner)
}

/// Enables forceful sorting of maps before serialization.
Expand Down Expand Up @@ -435,7 +439,7 @@ impl Settings {
fn add_redaction_impl(&mut self, selector: &str, replacement: Redaction) {
self._private_inner_mut().redactions.0.push((
Selector::parse(selector).unwrap().make_static(),
Rc::new(replacement),
Arc::new(replacement),
));
}

Expand All @@ -451,7 +455,7 @@ impl Settings {
pub fn add_dynamic_redaction<I, F>(&mut self, selector: &str, func: F)
where
I: Into<Content>,
F: Fn(Content, ContentPath<'_>) -> I + 'static,
F: Fn(Content, ContentPath<'_>) -> I + Send + Sync + 'static,
{
self.add_redaction(selector, dynamic_redaction(func));
}
Expand Down Expand Up @@ -584,7 +588,7 @@ impl Settings {
/// ```
pub fn bind_async<F: Future<Output = T>, T>(&self, future: F) -> impl Future<Output = T> {
struct BindingFuture<F> {
settings: Rc<ActualSettings>,
settings: Arc<ActualSettings>,
future: F,
}

Expand Down Expand Up @@ -660,7 +664,7 @@ impl Settings {
/// 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<Rc<ActualSettings>>,
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).
Expand All @@ -677,3 +681,13 @@ impl Drop for SettingsBindDropGuard {
})
}
}

// Prevent accidental removal of Send/Sync (which is a semver-breaking change).
const _: () = {
fn _assert_send_sync<T: Send + Sync>() {}
fn _assert() {
_assert_send_sync::<Settings>();
#[cfg(feature = "redactions")]
_assert_send_sync::<Redactions>();
}
};
Loading