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 $crate::_prepare_snapshot_for_redaction!(value, {$($k => $v),*}, $format)
252 };
253 $crate::_assert_snapshot_base!(transform=transform, $value $($arg)*);
254 }};
255 // If there's a name, redaction expressions, and debug_expr, capture and pass all to `_assert_snapshot_base`
256 (format=$format:ident, $name:expr, $value:expr, $(match ..)? {$($k:expr => $v:expr),* $(,)?}, $debug_expr:expr $(,)?) => {{
257 let transform = |value| {
258 $crate::_prepare_snapshot_for_redaction!(value, {$($k => $v),*}, $format)
259 };
260 $crate::_assert_snapshot_base!(transform=transform, $name, $value, $debug_expr);
261 }};
262 // If there's a name and redaction expressions, capture and pass to `_assert_snapshot_base`
263 (format=$format:ident, $name:expr, $value:expr, $(match ..)? {$($k:expr => $v:expr),* $(,)?} $(,)?) => {{
264 let transform = |value| {
265 $crate::_prepare_snapshot_for_redaction!(value, {$($k => $v),*}, $format)
266 };
267 $crate::_assert_snapshot_base!(transform=transform, $name, $value);
268 }};
269 // Capture serialization function and pass to `_assert_snapshot_base`
270 //
271 (format=$format:ident, $($arg:tt)*) => {{
272 let transform = |value| {$crate::_macro_support::serialize_value(
273 &value,
274 $crate::_macro_support::SerializationFormat::$format,
275 )};
276 $crate::_assert_snapshot_base!(transform=transform, $($arg)*);
277 }};
278}
279
280#[cfg(feature = "redactions")]
281#[doc(hidden)]
282#[macro_export]
283macro_rules! _prepare_snapshot_for_redaction {
284 ($value:expr, {$($k:expr => $v:expr),*}, $format:ident) => {
285 {
286 let vec = $crate::_macro_support::vec![
287 $((
288 $crate::_macro_support::Selector::parse($k).unwrap(),
289 $crate::_macro_support::Redaction::from($v)
290 ),)*
291 ];
292 $crate::_macro_support::serialize_value_redacted(
293 &$value,
294 &vec,
295 $crate::_macro_support::SerializationFormat::$format,
296 )
297 }
298 }
299}
300
301#[cfg(not(feature = "redactions"))]
302#[doc(hidden)]
303#[macro_export]
304macro_rules! _prepare_snapshot_for_redaction {
305 ($value:expr, {$($k:expr => $v:expr),*}, $format:ident) => {
306 compile_error!(
307 "insta was compiled without redactions support. Enable the `redactions` feature."
308 )
309 };
310}
311
312/// Asserts a [`Debug`] snapshot.
313///
314/// The value needs to implement the [`Debug`] trait. This is useful for
315/// simple values that do not implement the [`serde::Serialize`] trait, but does not
316/// permit redactions.
317///
318/// Debug is called with `"{:#?}"`, which means this uses pretty-print.
319#[macro_export]
320macro_rules! assert_debug_snapshot {
321 ($($arg:tt)*) => {
322 $crate::_assert_snapshot_base!(transform=|v| $crate::_macro_support::format!("{:#?}", v), $($arg)*)
323 };
324}
325
326/// Asserts a [`Debug`] snapshot in compact format.
327///
328/// The value needs to implement the [`Debug`] trait. This is useful for
329/// simple values that do not implement the [`serde::Serialize`] trait, but does not
330/// permit redactions.
331///
332/// Debug is called with `"{:?}"`, which means this does not use pretty-print.
333#[macro_export]
334macro_rules! assert_compact_debug_snapshot {
335 ($($arg:tt)*) => {
336 $crate::_assert_snapshot_base!(transform=|v| $crate::_macro_support::format!("{:?}", v), $($arg)*)
337 };
338}
339
340// A helper macro which takes a closure as `transform`, and runs the closure on
341// the value. This allows us to implement other macros with a small wrapper. All
342// snapshot macros eventually call this macro.
343//
344// This macro handles optional trailing commas.
345#[doc(hidden)]
346#[macro_export]
347macro_rules! _assert_snapshot_base {
348 // If there's an inline literal value, wrap the literal in a
349 // `ReferenceValue::Inline`, call self.
350 (transform=$transform:expr, $($arg:expr),*, @$snapshot:literal $(,)?) => {
351 $crate::_assert_snapshot_base!(
352 transform = $transform,
353 #[allow(clippy::needless_raw_string_hashes)]
354 $crate::_macro_support::InlineValue($snapshot),
355 $($arg),*
356 )
357 };
358 // If there's no debug_expr, use the stringified value, call self.
359 (transform=$transform:expr, $name:expr, $value:expr $(,)?) => {
360 $crate::_assert_snapshot_base!(transform = $transform, $name, $value, stringify!($value))
361 };
362 // If there's no name (and necessarily no debug expr), auto generate the
363 // name, call self.
364 (transform=$transform:expr, $value:expr $(,)?) => {
365 $crate::_assert_snapshot_base!(
366 transform = $transform,
367 $crate::_macro_support::AutoName,
368 $value
369 )
370 };
371 // The main macro body — every call to this macro should end up here.
372 (transform=$transform:expr, $name:expr, $value:expr, $debug_expr:expr $(,)?) => {
373 $crate::_macro_support::assert_snapshot(
374 (
375 $name,
376 #[allow(clippy::redundant_closure_call)]
377 $transform(&$value).as_str(),
378 ).into(),
379 $crate::_get_workspace_root!().as_path(),
380 $crate::_function_name!(),
381 $crate::_macro_support::module_path!(),
382 $crate::_macro_support::file!(),
383 $crate::_macro_support::line!(),
384 $debug_expr,
385 )
386 .unwrap()
387 };
388}
389
390/// (Experimental)
391/// Asserts a binary snapshot in the form of a [`Vec<u8>`].
392///
393/// The contents get stored in a separate file next to the metadata file. The extension for this
394/// file must be passed as part of the name. For an implicit snapshot name just an extension can be
395/// passed starting with a `.`.
396///
397/// This feature is considered experimental: we may make incompatible changes for the next couple
398/// of versions after 1.41.
399///
400/// Examples:
401///
402/// ```no_run
403/// // implicit name:
404/// insta::assert_binary_snapshot!(".txt", b"abcd".to_vec());
405///
406/// // named:
407/// insta::assert_binary_snapshot!("my_snapshot.bin", [0, 1, 2, 3].to_vec());
408/// ```
409#[macro_export]
410macro_rules! assert_binary_snapshot {
411 ($name_and_extension:expr, $value:expr $(,)?) => {
412 $crate::assert_binary_snapshot!($name_and_extension, $value, stringify!($value));
413 };
414
415 ($name_and_extension:expr, $value:expr, $debug_expr:expr $(,)?) => {
416 $crate::_macro_support::assert_snapshot(
417 $crate::_macro_support::BinarySnapshotValue {
418 name_and_extension: $name_and_extension,
419 content: $value,
420 }
421 .into(),
422 $crate::_get_workspace_root!().as_path(),
423 $crate::_function_name!(),
424 $crate::_macro_support::module_path!(),
425 $crate::_macro_support::file!(),
426 $crate::_macro_support::line!(),
427 $debug_expr,
428 )
429 .unwrap()
430 };
431}
432
433/// Asserts a [`Display`](std::fmt::Display) snapshot.
434///
435/// This is now deprecated, replaced by the more generic [`assert_snapshot!`](crate::assert_snapshot!)
436#[macro_export]
437#[deprecated = "use assert_snapshot!() instead"]
438macro_rules! assert_display_snapshot {
439 ($($arg:tt)*) => {
440 $crate::assert_snapshot!($($arg)*)
441 };
442}
443
444/// Asserts a [`String`] snapshot.
445///
446/// This is the simplest of all assertion methods.
447/// It accepts any value that implements [`Display`](std::fmt::Display).
448///
449/// ```no_run
450/// # use insta::*;
451/// // implicitly named
452/// assert_snapshot!("reference value to snapshot");
453/// // named
454/// assert_snapshot!("snapshot_name", "reference value to snapshot");
455/// // inline
456/// assert_snapshot!("reference value", @"reference value");
457/// ```
458///
459/// Optionally a third argument can be given as an expression to be stringified
460/// as the debug expression. For more information on this, check out
461/// <https://insta.rs/docs/snapshot-types/>.
462#[macro_export]
463macro_rules! assert_snapshot {
464 ($($arg:tt)*) => {
465 $crate::_assert_snapshot_base!(transform=|v| $crate::_macro_support::format!("{}", v), $($arg)*)
466 };
467}
468
469/// Settings configuration macro.
470///
471/// This macro lets you bind some [`Settings`](crate::Settings) temporarily. The first argument
472/// takes key value pairs that should be set, and the second is the block to
473/// execute. All settings can be set (`sort_maps => value` maps to `set_sort_maps(value)`).
474/// The exception are redactions, which can only be set to a vector this way.
475///
476/// This example:
477///
478/// ```rust
479/// insta::with_settings!({sort_maps => true}, {
480/// // run snapshot test here
481/// });
482/// ```
483///
484/// Is equivalent to the following:
485///
486/// ```rust
487/// # use insta::Settings;
488/// let mut settings = Settings::clone_current();
489/// settings.set_sort_maps(true);
490/// settings.bind(|| {
491/// // run snapshot test here
492/// });
493/// ```
494///
495/// Note: before insta 0.17, this macro used
496/// [`Settings::new`](crate::Settings::new) which meant that original settings
497/// were always reset rather than extended.
498#[macro_export]
499macro_rules! with_settings {
500 ({$($k:ident => $v:expr),*$(,)?}, $body:block) => {{
501 let mut settings = $crate::Settings::clone_current();
502 $(
503 settings._private_inner_mut().$k($v);
504 )*
505 settings.bind(|| $body)
506 }}
507}
508
509/// Executes a closure for all input files matching a glob.
510///
511/// The closure is passed the path to the file. You can use [`std::fs::read_to_string`]
512/// or similar functions to load the file and process it.
513///
514/// ```
515/// # use insta::{assert_snapshot, glob, Settings};
516/// # let mut settings = Settings::clone_current();
517/// # settings.set_allow_empty_glob(true);
518/// # let _dropguard = settings.bind_to_scope();
519/// use std::fs;
520///
521/// glob!("inputs/*.txt", |path| {
522/// let input = fs::read_to_string(path).unwrap();
523/// assert_snapshot!(input.to_uppercase());
524/// });
525/// ```
526///
527/// The `INSTA_GLOB_FILTER` environment variable can be set to only execute certain files.
528/// The format of the filter is a semicolon separated filter. For instance by setting
529/// `INSTA_GLOB_FILTER` to `foo-*txt;bar-*.txt` only files starting with `foo-` or `bar-`
530/// end ending in `.txt` will be executed. When using `cargo-insta` the `--glob-filter`
531/// option can be used instead.
532///
533/// Another effect of the globbing system is that snapshot failures within the glob macro
534/// are deferred until the end of of it. In other words this means that each snapshot
535/// assertion within the `glob!` block are reported. It can be disabled by setting
536/// `INSTA_GLOB_FAIL_FAST` environment variable to `1`.
537///
538/// Note: Parent directory traversal patterns (e.g., "../**/*.rs") are not supported in the
539/// two-argument form of this macro currently. If you need to access parent
540/// directories, use the three-argument version of this macro instead.
541///
542/// A three-argument version of this macro allows specifying a base directory
543/// for the glob to start in. This allows globbing in arbitrary directories,
544/// including parent directories:
545///
546/// ```
547/// # use insta::{assert_snapshot, glob, Settings};
548/// # let mut settings = Settings::clone_current();
549/// # settings.set_allow_empty_glob(true);
550/// # let _dropguard = settings.bind_to_scope();
551/// use std::fs;
552///
553/// glob!("../test_data", "inputs/*.txt", |path| {
554/// let input = fs::read_to_string(path).unwrap();
555/// assert_snapshot!(input.to_uppercase());
556/// });
557/// ```
558#[cfg(feature = "glob")]
559#[cfg_attr(docsrs, doc(cfg(feature = "glob")))]
560#[macro_export]
561macro_rules! glob {
562 // TODO: I think we could remove the three-argument version of this macro
563 // and just support a pattern such as
564 // `glob!("../test_data/inputs/*.txt"...`.
565 ($base_path:expr, $glob:expr, $closure:expr) => {{
566 use $crate::_macro_support::path::Path;
567
568 let base = $crate::_get_workspace_root!()
569 .join(Path::new(file!()).parent().unwrap())
570 .join($base_path)
571 .to_path_buf();
572
573 // we try to canonicalize but on some platforms (eg: wasm) that might not work, so
574 // we instead silently fall back.
575 let base = base.canonicalize().unwrap_or_else(|_| base);
576 $crate::_macro_support::glob_exec(
577 $crate::_get_workspace_root!().as_path(),
578 &base,
579 $glob,
580 $closure,
581 );
582 }};
583
584 ($glob:expr, $closure:expr) => {{
585 $crate::glob!(".", $glob, $closure)
586 }};
587}
588
589/// Utility macro to permit a multi-snapshot run where all snapshots match.
590///
591/// Within this block, insta will allow an assertion to be run more than once
592/// (even inline) without generating another snapshot. Instead it will assert
593/// that snapshot expressions visited more than once are matching.
594///
595/// ```rust
596/// insta::allow_duplicates! {
597/// for x in (0..10).step_by(2) {
598/// let is_even = x % 2 == 0;
599/// insta::assert_debug_snapshot!(is_even, @"true");
600/// }
601/// }
602/// ```
603///
604/// The first snapshot assertion will be used as a gold master and every further
605/// assertion will be checked against it. If they don't match the assertion will
606/// fail.
607#[macro_export]
608macro_rules! allow_duplicates {
609 ($($x:tt)*) => {
610 $crate::_macro_support::with_allow_duplicates(|| {
611 $($x)*
612 })
613 }
614}