insta/
macros.rs

1/// Utility macro to return the name of the current function.
2#[doc(hidden)]
3#[macro_export]
4macro_rules! _function_name {
5    () => {{
6        fn f() {}
7        fn type_name_of_val<T>(_: T) -> &'static str {
8            $crate::_macro_support::any::type_name::<T>()
9        }
10        let mut name = type_name_of_val(f).strip_suffix("::f").unwrap_or("");
11        while let Some(rest) = name.strip_suffix("::{{closure}}") {
12            name = rest;
13        }
14        name
15    }};
16}
17
18#[doc(hidden)]
19#[macro_export]
20macro_rules! _get_workspace_root {
21    () => {{
22        use $crate::_macro_support::{env, option_env};
23
24        // Note the `env!("CARGO_MANIFEST_DIR")` needs to be in the macro (in
25        // contrast to a function in insta) because the macro needs to capture
26        // the value in the caller library, an exclusive property of macros.
27        // By default the `CARGO_MANIFEST_DIR` environment variable is used as the workspace root.
28        // If the `INSTA_WORKSPACE_ROOT` environment variable is set at compile time it will override the default.
29        // This can be useful to avoid including local paths in the binary.
30        const WORKSPACE_ROOT: $crate::_macro_support::Workspace = if let Some(root) = option_env!("INSTA_WORKSPACE_ROOT") {
31            $crate::_macro_support::Workspace::UseAsIs(root)
32        } else {
33            $crate::_macro_support::Workspace::DetectWithCargo(env!("CARGO_MANIFEST_DIR"))
34        };
35        $crate::_macro_support::get_cargo_workspace(WORKSPACE_ROOT)
36    }};
37}
38
39/// Asserts a [`serde::Serialize`] snapshot in CSV format.
40///
41/// **Feature:** `csv` (disabled by default)
42///
43/// This works exactly like [`assert_yaml_snapshot!`](crate::assert_yaml_snapshot!)
44/// but serializes in [CSV](https://github.com/burntsushi/rust-csv) format instead of
45/// YAML.
46///
47/// Example:
48///
49/// ```no_run
50/// insta::assert_csv_snapshot!(vec![1, 2, 3]);
51/// ```
52///
53/// The third argument to the macro can be an object expression for redaction.
54/// It's in the form `{ selector => replacement }` or `match .. { selector => replacement }`.
55/// For more information about redactions refer to the [redactions feature in
56/// the guide](https://insta.rs/docs/redactions/).
57///
58/// The snapshot name is optional but can be provided as first argument.
59#[cfg(feature = "csv")]
60#[cfg_attr(docsrs, doc(cfg(feature = "csv")))]
61#[macro_export]
62macro_rules! assert_csv_snapshot {
63    ($($arg:tt)*) => {
64        $crate::_assert_serialized_snapshot!(format=Csv, $($arg)*);
65    };
66}
67
68/// Asserts a [`serde::Serialize`] snapshot in TOML format.
69///
70/// **Feature:** `toml` (disabled by default)
71///
72/// This works exactly like [`assert_yaml_snapshot!`](crate::assert_yaml_snapshot!)
73/// but serializes in [TOML](https://github.com/alexcrichton/toml-rs) format instead of
74/// YAML.  Note that TOML cannot represent all values due to limitations in the
75/// format.
76///
77/// Example:
78///
79/// ```no_run
80/// insta::assert_toml_snapshot!(vec![1, 2, 3]);
81/// ```
82///
83/// The third argument to the macro can be an object expression for redaction.
84/// It's in the form `{ selector => replacement }` or `match .. { selector => replacement }`.
85/// For more information about redactions refer to the [redactions feature in
86/// the guide](https://insta.rs/docs/redactions/).
87///
88/// The snapshot name is optional but can be provided as first argument.
89#[cfg(feature = "toml")]
90#[cfg_attr(docsrs, doc(cfg(feature = "toml")))]
91#[macro_export]
92macro_rules! assert_toml_snapshot {
93    ($($arg:tt)*) => {
94        $crate::_assert_serialized_snapshot!(format=Toml, $($arg)*);
95    };
96}
97
98/// Asserts a [`serde::Serialize`] snapshot in YAML format.
99///
100/// **Feature:** `yaml`
101///
102/// The value needs to implement the [`serde::Serialize`] trait and the snapshot
103/// will be serialized in YAML format.  This does mean that unlike the debug
104/// snapshot variant the type of the value does not appear in the output.
105/// You can however use the [`assert_ron_snapshot!`](crate::assert_ron_snapshot!) macro to dump out
106/// the value in [RON](https://github.com/ron-rs/ron/) format which retains some
107/// type information for more accurate comparisons.
108///
109/// Example:
110///
111/// ```no_run
112/// # use insta::*;
113/// assert_yaml_snapshot!(vec![1, 2, 3]);
114/// ```
115///
116/// Unlike the [`assert_debug_snapshot!`](crate::assert_debug_snapshot!)
117/// macro, this one has a secondary mode where redactions can be defined.
118///
119/// The third argument to the macro can be an object expression for redaction.
120/// It's in the form `{ selector => replacement }` or `match .. { selector => replacement }`.
121/// For more information about redactions refer to the [redactions feature in
122/// the guide](https://insta.rs/docs/redactions/).
123///
124/// Example:
125///
126#[cfg_attr(feature = "redactions", doc = " ```no_run")]
127#[cfg_attr(not(feature = "redactions"), doc = " ```ignore")]
128/// # use insta::*; use serde::Serialize;
129/// # #[derive(Serialize)] struct Value; let value = Value;
130/// assert_yaml_snapshot!(value, {
131///     ".key.to.redact" => "[replacement value]",
132///     ".another.key.*.to.redact" => 42
133/// });
134/// ```
135///
136/// The replacement value can be a string, integer or any other primitive value.
137///
138/// For inline usage the format is `(expression, @reference_value)` where the
139/// reference value must be a string literal.  If you make the initial snapshot
140/// just use an empty string (`@""`).
141///
142/// The snapshot name is optional but can be provided as first argument.
143#[cfg(feature = "yaml")]
144#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
145#[macro_export]
146macro_rules! assert_yaml_snapshot {
147    ($($arg:tt)*) => {
148        $crate::_assert_serialized_snapshot!(format=Yaml, $($arg)*);
149    };
150}
151
152/// Asserts a [`serde::Serialize`] snapshot in RON format.
153///
154/// **Feature:** `ron` (disabled by default)
155///
156/// This works exactly like [`assert_yaml_snapshot!`](crate::assert_yaml_snapshot!)
157/// but serializes in [RON](https://github.com/ron-rs/ron/) format instead of
158/// YAML which retains some type information for more accurate comparisons.
159///
160/// Example:
161///
162/// ```no_run
163/// # use insta::*;
164/// assert_ron_snapshot!(vec![1, 2, 3]);
165/// ```
166///
167/// The third argument to the macro can be an object expression for redaction.
168/// It's in the form `{ selector => replacement }` or `match .. { selector => replacement }`.
169/// For more information about redactions refer to the [redactions feature in
170/// the guide](https://insta.rs/docs/redactions/).
171///
172/// The snapshot name is optional but can be provided as first argument.
173#[cfg(feature = "ron")]
174#[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
175#[macro_export]
176macro_rules! assert_ron_snapshot {
177    ($($arg:tt)*) => {
178        $crate::_assert_serialized_snapshot!(format=Ron, $($arg)*);
179    };
180}
181
182/// Asserts a [`serde::Serialize`] snapshot in JSON format.
183///
184/// **Feature:** `json`
185///
186/// This works exactly like [`assert_yaml_snapshot!`](crate::assert_yaml_snapshot!) but serializes in JSON format.
187/// This is normally not recommended because it makes diffs less reliable, but it can
188/// be useful for certain specialized situations.
189///
190/// Example:
191///
192/// ```no_run
193/// # use insta::*;
194/// assert_json_snapshot!(vec![1, 2, 3]);
195/// ```
196///
197/// The third argument to the macro can be an object expression for redaction.
198/// It's in the form `{ selector => replacement }` or `match .. { selector => replacement }`.
199/// For more information about redactions refer to the [redactions feature in
200/// the guide](https://insta.rs/docs/redactions/).
201///
202/// The snapshot name is optional but can be provided as first argument.
203#[cfg(feature = "json")]
204#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
205#[macro_export]
206macro_rules! assert_json_snapshot {
207    ($($arg:tt)*) => {
208        $crate::_assert_serialized_snapshot!(format=Json, $($arg)*);
209    };
210}
211
212/// Asserts a [`serde::Serialize`] snapshot in compact JSON format.
213///
214/// **Feature:** `json`
215///
216/// This works exactly like [`assert_json_snapshot!`](crate::assert_json_snapshot!) but serializes into a single
217/// line for as long as the output is less than 120 characters.  This can be useful
218/// in cases where you are working with small result outputs but comes at the cost
219/// of slightly worse diffing behavior.
220///
221/// Example:
222///
223/// ```no_run
224/// # use insta::*;
225/// assert_compact_json_snapshot!(vec![1, 2, 3]);
226/// ```
227///
228/// The third argument to the macro can be an object expression for redaction.
229/// It's in the form `{ selector => replacement }` or `match .. { selector => replacement }`.
230/// For more information about redactions refer to the [redactions feature in
231/// the guide](https://insta.rs/docs/redactions/).
232///
233/// The snapshot name is optional but can be provided as first argument.
234#[cfg(feature = "json")]
235#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
236#[macro_export]
237macro_rules! assert_compact_json_snapshot {
238    ($($arg:tt)*) => {
239        $crate::_assert_serialized_snapshot!(format=JsonCompact, $($arg)*);
240    };
241}
242
243// This macro handles optional trailing commas.
244#[doc(hidden)]
245#[macro_export]
246macro_rules! _assert_serialized_snapshot {
247    // If there are redaction expressions, capture the redactions expressions
248    // and pass to `_assert_snapshot_base`
249    (format=$format:ident, $value:expr, $(match ..)? {$($k:expr => $v:expr),* $(,)?} $($arg:tt)*) => {{
250        let transform = |value| {
251            let (_, value) = $crate::_prepare_snapshot_for_redaction!(value, {$($k => $v),*}, $format);
252            value
253        };
254        $crate::_assert_snapshot_base!(transform=transform, $value $($arg)*);
255    }};
256    // If there's a name and redaction expressions, capture and pass to `_assert_snapshot_base`
257    (format=$format:ident, $name:expr, $value:expr, $(match ..)? {$($k:expr => $v:expr),* $(,)?} $(,)?) => {{
258        let transform = |value| {
259            let (_, value) = $crate::_prepare_snapshot_for_redaction!(value, {$($k => $v),*}, $format);
260            value
261        };
262        $crate::_assert_snapshot_base!(transform=transform, $name, $value);
263    }};
264    // Capture serialization function and pass to `_assert_snapshot_base`
265    //
266    (format=$format:ident, $($arg:tt)*) => {{
267        let transform = |value| {$crate::_macro_support::serialize_value(
268            &value,
269            $crate::_macro_support::SerializationFormat::$format,
270        )};
271        $crate::_assert_snapshot_base!(transform = transform, $($arg)*);
272    }};
273}
274
275#[cfg(feature = "redactions")]
276#[doc(hidden)]
277#[macro_export]
278macro_rules! _prepare_snapshot_for_redaction {
279    ($value:expr, {$($k:expr => $v:expr),*}, $format:ident) => {
280        {
281            let vec = $crate::_macro_support::vec![
282                $((
283                    $crate::_macro_support::Selector::parse($k).unwrap(),
284                    $crate::_macro_support::Redaction::from($v)
285                ),)*
286            ];
287            let value = $crate::_macro_support::serialize_value_redacted(
288                &$value,
289                &vec,
290                $crate::_macro_support::SerializationFormat::$format,
291            );
292            (vec, value)
293        }
294    }
295}
296
297#[cfg(not(feature = "redactions"))]
298#[doc(hidden)]
299#[macro_export]
300macro_rules! _prepare_snapshot_for_redaction {
301    ($value:expr, {$($k:expr => $v:expr),*}, $format:ident) => {
302        compile_error!(
303            "insta was compiled without redactions support. Enable the `redactions` feature."
304        )
305    };
306}
307
308/// Asserts a [`Debug`] snapshot.
309///
310/// The value needs to implement the [`Debug`] trait.  This is useful for
311/// simple values that do not implement the [`serde::Serialize`] trait, but does not
312/// permit redactions.
313///
314/// Debug is called with `"{:#?}"`, which means this uses pretty-print.
315#[macro_export]
316macro_rules! assert_debug_snapshot {
317    ($($arg:tt)*) => {
318        $crate::_assert_snapshot_base!(transform=|v| $crate::_macro_support::format!("{:#?}", v), $($arg)*)
319    };
320}
321
322/// Asserts a [`Debug`] snapshot in compact format.
323///
324/// The value needs to implement the [`Debug`] trait.  This is useful for
325/// simple values that do not implement the [`serde::Serialize`] trait, but does not
326/// permit redactions.
327///
328/// Debug is called with `"{:?}"`, which means this does not use pretty-print.
329#[macro_export]
330macro_rules! assert_compact_debug_snapshot {
331    ($($arg:tt)*) => {
332        $crate::_assert_snapshot_base!(transform=|v| $crate::_macro_support::format!("{:?}", v), $($arg)*)
333    };
334}
335
336// A helper macro which takes a closure as `transform`, and runs the closure on
337// the value. This allows us to implement other macros with a small wrapper. All
338// snapshot macros eventually call this macro.
339//
340// This macro handles optional trailing commas.
341#[doc(hidden)]
342#[macro_export]
343macro_rules! _assert_snapshot_base {
344    // If there's an inline literal value, wrap the literal in a
345    // `ReferenceValue::Inline`, call self.
346    (transform=$transform:expr, $($arg:expr),*, @$snapshot:literal $(,)?) => {
347        $crate::_assert_snapshot_base!(
348            transform = $transform,
349            #[allow(clippy::needless_raw_string_hashes)]
350            $crate::_macro_support::InlineValue($snapshot),
351            $($arg),*
352        )
353    };
354    // If there's no debug_expr, use the stringified value, call self.
355    (transform=$transform:expr, $name:expr, $value:expr $(,)?) => {
356        $crate::_assert_snapshot_base!(transform = $transform, $name, $value, stringify!($value))
357    };
358    // If there's no name (and necessarily no debug expr), auto generate the
359    // name, call self.
360    (transform=$transform:expr, $value:expr $(,)?) => {
361        $crate::_assert_snapshot_base!(
362            transform = $transform,
363            $crate::_macro_support::AutoName,
364            $value
365        )
366    };
367    // The main macro body — every call to this macro should end up here.
368    (transform=$transform:expr, $name:expr, $value:expr, $debug_expr:expr $(,)?) => {
369        $crate::_macro_support::assert_snapshot(
370            (
371                $name,
372                #[allow(clippy::redundant_closure_call)]
373                $transform(&$value).as_str(),
374            ).into(),
375            $crate::_get_workspace_root!().as_path(),
376            $crate::_function_name!(),
377            $crate::_macro_support::module_path!(),
378            $crate::_macro_support::file!(),
379            $crate::_macro_support::line!(),
380            $debug_expr,
381        )
382        .unwrap()
383    };
384}
385
386/// (Experimental)
387/// Asserts a binary snapshot in the form of a [`Vec<u8>`].
388///
389/// The contents get stored in a separate file next to the metadata file. The extension for this
390/// file must be passed as part of the name. For an implicit snapshot name just an extension can be
391/// passed starting with a `.`.
392///
393/// This feature is considered experimental: we may make incompatible changes for the next couple
394/// of versions after 1.41.
395///
396/// Examples:
397///
398/// ```no_run
399/// // implicit name:
400/// insta::assert_binary_snapshot!(".txt", b"abcd".to_vec());
401///
402/// // named:
403/// insta::assert_binary_snapshot!("my_snapshot.bin", [0, 1, 2, 3].to_vec());
404/// ```
405#[macro_export]
406macro_rules! assert_binary_snapshot {
407    ($name_and_extension:expr, $value:expr $(,)?) => {
408        $crate::assert_binary_snapshot!($name_and_extension, $value, stringify!($value));
409    };
410
411    ($name_and_extension:expr, $value:expr, $debug_expr:expr $(,)?) => {
412        $crate::_macro_support::assert_snapshot(
413            $crate::_macro_support::BinarySnapshotValue {
414                name_and_extension: $name_and_extension,
415                content: $value,
416            }
417            .into(),
418            $crate::_get_workspace_root!().as_path(),
419            $crate::_function_name!(),
420            $crate::_macro_support::module_path!(),
421            $crate::_macro_support::file!(),
422            $crate::_macro_support::line!(),
423            $debug_expr,
424        )
425        .unwrap()
426    };
427}
428
429/// Asserts a [`Display`](std::fmt::Display) snapshot.
430///
431/// This is now deprecated, replaced by the more generic [`assert_snapshot!`](crate::assert_snapshot!)
432#[macro_export]
433#[deprecated = "use assert_snapshot!() instead"]
434macro_rules! assert_display_snapshot {
435    ($($arg:tt)*) => {
436        $crate::assert_snapshot!($($arg)*)
437    };
438}
439
440/// Asserts a [`String`] snapshot.
441///
442/// This is the simplest of all assertion methods.
443/// It accepts any value that implements [`Display`](std::fmt::Display).
444///
445/// ```no_run
446/// # use insta::*;
447/// // implicitly named
448/// assert_snapshot!("reference value to snapshot");
449/// // named
450/// assert_snapshot!("snapshot_name", "reference value to snapshot");
451/// // inline
452/// assert_snapshot!("reference value", @"reference value");
453/// ```
454///
455/// Optionally a third argument can be given as an expression to be stringified
456/// as the debug expression.  For more information on this, check out
457/// <https://insta.rs/docs/snapshot-types/>.
458#[macro_export]
459macro_rules! assert_snapshot {
460    ($($arg:tt)*) => {
461        $crate::_assert_snapshot_base!(transform=|v| $crate::_macro_support::format!("{}", v), $($arg)*)
462    };
463}
464
465/// Settings configuration macro.
466///
467/// This macro lets you bind some [`Settings`](crate::Settings) temporarily.  The first argument
468/// takes key value pairs that should be set, the second is the block to
469/// execute.  All settings can be set (`sort_maps => value` maps to `set_sort_maps(value)`).
470/// The exception are redactions which can only be set to a vector this way.
471///
472/// This example:
473///
474/// ```rust
475/// insta::with_settings!({sort_maps => true}, {
476///     // run snapshot test here
477/// });
478/// ```
479///
480/// Is equivalent to the following:
481///
482/// ```rust
483/// # use insta::Settings;
484/// let mut settings = Settings::clone_current();
485/// settings.set_sort_maps(true);
486/// settings.bind(|| {
487///     // run snapshot test here
488/// });
489/// ```
490///
491/// Note: before insta 0.17 this macro used
492/// [`Settings::new`](crate::Settings::new) which meant that original settings
493/// were always reset rather than extended.
494#[macro_export]
495macro_rules! with_settings {
496    ({$($k:ident => $v:expr),*$(,)?}, $body:block) => {{
497        let mut settings = $crate::Settings::clone_current();
498        $(
499            settings._private_inner_mut().$k($v);
500        )*
501        settings.bind(|| $body)
502    }}
503}
504
505/// Executes a closure for all input files matching a glob.
506///
507/// The closure is passed the path to the file.  You can use [`std::fs::read_to_string`]
508/// or similar functions to load the file and process it.
509///
510/// ```
511/// # use insta::{assert_snapshot, glob, Settings};
512/// # let mut settings = Settings::clone_current();
513/// # settings.set_allow_empty_glob(true);
514/// # let _dropguard = settings.bind_to_scope();
515/// use std::fs;
516///
517/// glob!("inputs/*.txt", |path| {
518///     let input = fs::read_to_string(path).unwrap();
519///     assert_snapshot!(input.to_uppercase());
520/// });
521/// ```
522///
523/// The `INSTA_GLOB_FILTER` environment variable can be set to only execute certain files.
524/// The format of the filter is a semicolon separated filter.  For instance by setting
525/// `INSTA_GLOB_FILTER` to `foo-*txt;bar-*.txt` only files starting with `foo-` or `bar-`
526/// end ending in `.txt` will be executed.  When using `cargo-insta` the `--glob-filter`
527/// option can be used instead.
528///
529/// Another effect of the globbing system is that snapshot failures within the glob macro
530/// are deferred until the end of of it.  In other words this means that each snapshot
531/// assertion within the `glob!` block are reported.  It can be disabled by setting
532/// `INSTA_GLOB_FAIL_FAST` environment variable to `1`.
533///
534/// A three-argument version of this macro allows specifying a base directory
535/// for the glob to start in. This allows globbing in arbitrary directories,
536/// including parent directories:
537///
538/// ```
539/// # use insta::{assert_snapshot, glob, Settings};
540/// # let mut settings = Settings::clone_current();
541/// # settings.set_allow_empty_glob(true);
542/// # let _dropguard = settings.bind_to_scope();
543/// use std::fs;
544///
545/// glob!("../test_data", "inputs/*.txt", |path| {
546///     let input = fs::read_to_string(path).unwrap();
547///     assert_snapshot!(input.to_uppercase());
548/// });
549/// ```
550#[cfg(feature = "glob")]
551#[cfg_attr(docsrs, doc(cfg(feature = "glob")))]
552#[macro_export]
553macro_rules! glob {
554    // TODO: I think we could remove the three-argument version of this macro
555    // and just support a pattern such as
556    // `glob!("../test_data/inputs/*.txt"...`.
557    ($base_path:expr, $glob:expr, $closure:expr) => {{
558        use $crate::_macro_support::path::Path;
559
560        let base = $crate::_get_workspace_root!()
561            .join(Path::new(file!()).parent().unwrap())
562            .join($base_path)
563            .to_path_buf();
564
565        // we try to canonicalize but on some platforms (eg: wasm) that might not work, so
566        // we instead silently fall back.
567        let base = base.canonicalize().unwrap_or_else(|_| base);
568        $crate::_macro_support::glob_exec(
569            $crate::_get_workspace_root!().as_path(),
570            &base,
571            $glob,
572            $closure,
573        );
574    }};
575
576    ($glob:expr, $closure:expr) => {{
577        $crate::glob!(".", $glob, $closure)
578    }};
579}
580
581/// Utility macro to permit a multi-snapshot run where all snapshots match.
582///
583/// Within this block, insta will allow an assertion to be run more than once
584/// (even inline) without generating another snapshot.  Instead it will assert
585/// that snapshot expressions visited more than once are matching.
586///
587/// ```rust
588/// insta::allow_duplicates! {
589///     for x in (0..10).step_by(2) {
590///         let is_even = x % 2 == 0;
591///         insta::assert_debug_snapshot!(is_even, @"true");
592///     }
593/// }
594/// ```
595///
596/// The first snapshot assertion will be used as a gold master and every further
597/// assertion will be checked against it.  If they don't match the assertion will
598/// fail.
599#[macro_export]
600macro_rules! allow_duplicates {
601    ($($x:tt)*) => {
602        $crate::_macro_support::with_allow_duplicates(|| {
603            $($x)*
604        })
605    }
606}