1use std::cell::RefCell;
2use std::collections::{BTreeMap, BTreeSet};
3use std::error::Error;
4use std::fs;
5use std::io::{ErrorKind, Write};
6use std::path::{Path, PathBuf};
7use std::rc::Rc;
8use std::str;
9use std::sync::{Arc, Mutex};
10use std::{borrow::Cow, env};
11
12use crate::settings::Settings;
13use crate::snapshot::{
14 MetaData, PendingInlineSnapshot, Snapshot, SnapshotContents, SnapshotKind, TextSnapshotContents,
15};
16use crate::utils::{path_to_storage, style};
17use crate::{env::get_tool_config, output::SnapshotPrinter};
18use crate::{
19 env::{
20 memoize_snapshot_file, snapshot_update_behavior, OutputBehavior, SnapshotUpdateBehavior,
21 ToolConfig,
22 },
23 snapshot::TextSnapshotKind,
24};
25
26use once_cell::sync::Lazy;
27
28static TEST_NAME_COUNTERS: Lazy<Mutex<BTreeMap<String, usize>>> =
29 Lazy::new(|| Mutex::new(BTreeMap::new()));
30static TEST_NAME_CLASH_DETECTION: Lazy<Mutex<BTreeMap<String, bool>>> =
31 Lazy::new(|| Mutex::new(BTreeMap::new()));
32static INLINE_DUPLICATES: Lazy<Mutex<BTreeSet<String>>> = Lazy::new(|| Mutex::new(BTreeSet::new()));
33
34thread_local! {
35 static RECORDED_DUPLICATES: RefCell<Vec<BTreeMap<String, Snapshot>>> = RefCell::default()
36}
37
38#[macro_export]
41macro_rules! elog {
42 () => (write!(std::io::stderr()).ok());
43 ($($arg:tt)*) => ({
44 writeln!(std::io::stderr(), $($arg)*).ok();
45 })
46}
47#[cfg(feature = "glob")]
48macro_rules! print_or_panic {
49 ($fail_fast:expr, $($tokens:tt)*) => {{
50 if (!$fail_fast) {
51 eprintln!($($tokens)*);
52 eprintln!();
53 } else {
54 panic!($($tokens)*);
55 }
56 }}
57}
58
59#[derive(Debug)]
65pub struct AutoName;
66
67pub struct InlineValue<'a>(pub &'a str);
68
69type SnapshotName<'a> = Option<Cow<'a, str>>;
71
72pub struct BinarySnapshotValue<'a> {
73 pub name_and_extension: &'a str,
74 pub content: Vec<u8>,
75}
76
77pub enum SnapshotValue<'a> {
78 FileText {
80 name: SnapshotName<'a>,
81
82 content: &'a str,
84 },
85
86 InlineText {
88 reference_content: &'a str,
90
91 content: &'a str,
93 },
94
95 Binary {
97 name: SnapshotName<'a>,
98
99 content: Vec<u8>,
101
102 extension: &'a str,
104 },
105}
106
107impl<'a> From<(AutoName, &'a str)> for SnapshotValue<'a> {
108 fn from((_, content): (AutoName, &'a str)) -> Self {
109 SnapshotValue::FileText {
110 name: None,
111 content,
112 }
113 }
114}
115
116impl<'a> From<(Option<String>, &'a str)> for SnapshotValue<'a> {
117 fn from((name, content): (Option<String>, &'a str)) -> Self {
118 SnapshotValue::FileText {
119 name: name.map(Cow::Owned),
120 content,
121 }
122 }
123}
124
125impl<'a> From<(String, &'a str)> for SnapshotValue<'a> {
126 fn from((name, content): (String, &'a str)) -> Self {
127 SnapshotValue::FileText {
128 name: Some(Cow::Owned(name)),
129 content,
130 }
131 }
132}
133
134impl<'a> From<(Option<&'a str>, &'a str)> for SnapshotValue<'a> {
135 fn from((name, content): (Option<&'a str>, &'a str)) -> Self {
136 SnapshotValue::FileText {
137 name: name.map(Cow::Borrowed),
138 content,
139 }
140 }
141}
142
143impl<'a> From<(&'a str, &'a str)> for SnapshotValue<'a> {
144 fn from((name, content): (&'a str, &'a str)) -> Self {
145 SnapshotValue::FileText {
146 name: Some(Cow::Borrowed(name)),
147 content,
148 }
149 }
150}
151
152impl<'a> From<(InlineValue<'a>, &'a str)> for SnapshotValue<'a> {
153 fn from((InlineValue(reference_content), content): (InlineValue<'a>, &'a str)) -> Self {
154 SnapshotValue::InlineText {
155 reference_content,
156 content,
157 }
158 }
159}
160
161impl<'a> From<BinarySnapshotValue<'a>> for SnapshotValue<'a> {
162 fn from(
163 BinarySnapshotValue {
164 name_and_extension,
165 content,
166 }: BinarySnapshotValue<'a>,
167 ) -> Self {
168 let (name, extension) = name_and_extension.split_once('.').unwrap_or_else(|| {
169 panic!("\"{name_and_extension}\" does not match the format \"name.extension\"",)
170 });
171
172 let name = if name.is_empty() {
173 None
174 } else {
175 Some(Cow::Borrowed(name))
176 };
177
178 SnapshotValue::Binary {
179 name,
180 extension,
181 content,
182 }
183 }
184}
185
186fn is_doctest(function_name: &str) -> bool {
187 function_name.starts_with("rust_out::main::_doctest")
188}
189
190fn detect_snapshot_name(function_name: &str, module_path: &str) -> Result<String, &'static str> {
191 let name = function_name.rsplit("::").next().unwrap();
193
194 let (name, test_prefixed) = if let Some(stripped) = name.strip_prefix("test_") {
195 (stripped, true)
196 } else {
197 (name, false)
198 };
199
200 let name = add_suffix_to_snapshot_name(Cow::Borrowed(name));
202 let key = format!("{}::{}", module_path.replace("::", "__"), name);
203
204 let mut name_clash_detection = TEST_NAME_CLASH_DETECTION
207 .lock()
208 .unwrap_or_else(|x| x.into_inner());
209 match name_clash_detection.get(&key) {
210 None => {
211 name_clash_detection.insert(key.clone(), test_prefixed);
212 }
213 Some(&was_test_prefixed) => {
214 if was_test_prefixed != test_prefixed {
215 panic!(
216 "Insta snapshot name clash detected between '{name}' \
217 and 'test_{name}' in '{module_path}'. Rename one function."
218 );
219 }
220 }
221 }
222
223 if allow_duplicates() {
226 return Ok(name.to_string());
227 }
228
229 let mut counters = TEST_NAME_COUNTERS.lock().unwrap_or_else(|x| x.into_inner());
232 let test_idx = counters.get(&key).cloned().unwrap_or(0) + 1;
233 let rv = if test_idx == 1 {
234 name.to_string()
235 } else {
236 format!("{name}-{test_idx}")
237 };
238 counters.insert(key, test_idx);
239
240 Ok(rv)
241}
242
243fn add_suffix_to_snapshot_name(name: Cow<'_, str>) -> Cow<'_, str> {
245 Settings::with(|settings| {
246 settings
247 .snapshot_suffix()
248 .map(|suffix| Cow::Owned(format!("{name}@{suffix}")))
249 .unwrap_or_else(|| name)
250 })
251}
252
253fn get_snapshot_filename(
254 module_path: &str,
255 assertion_file: &str,
256 snapshot_name: &str,
257 cargo_workspace: &Path,
258 is_doctest: bool,
259) -> PathBuf {
260 let root = Path::new(cargo_workspace);
261 let base = Path::new(assertion_file);
262 Settings::with(|settings| {
263 root.join(base.parent().unwrap())
264 .join(settings.snapshot_path())
265 .join({
266 use std::fmt::Write;
267 let mut f = String::new();
268 if settings.prepend_module_to_snapshot() {
269 if is_doctest {
270 write!(
271 &mut f,
272 "doctest_{}__",
273 base.file_name()
274 .unwrap()
275 .to_string_lossy()
276 .replace('.', "_")
277 )
278 .unwrap();
279 } else {
280 write!(&mut f, "{}__", module_path.replace("::", "__")).unwrap();
281 }
282 }
283 write!(
284 &mut f,
285 "{}.snap",
286 snapshot_name.replace(&['/', '\\'][..], "__")
287 )
288 .unwrap();
289 f
290 })
291 })
292}
293
294#[derive(Debug)]
298struct SnapshotAssertionContext<'a> {
299 tool_config: Arc<ToolConfig>,
300 workspace: &'a Path,
301 module_path: &'a str,
302 snapshot_name: Option<Cow<'a, str>>,
303 snapshot_file: Option<PathBuf>,
304 duplication_key: Option<String>,
305 old_snapshot: Option<Snapshot>,
306 pending_snapshots_path: Option<PathBuf>,
307 assertion_file: &'a str,
308 assertion_line: u32,
309 is_doctest: bool,
310 snapshot_kind: SnapshotKind,
311}
312
313impl<'a> SnapshotAssertionContext<'a> {
314 fn prepare(
315 new_snapshot_value: &SnapshotValue<'a>,
316 workspace: &'a Path,
317 function_name: &'a str,
318 module_path: &'a str,
319 assertion_file: &'a str,
320 assertion_line: u32,
321 ) -> Result<SnapshotAssertionContext<'a>, Box<dyn Error>> {
322 let tool_config = get_tool_config(workspace);
323 let snapshot_name;
324 let mut duplication_key = None;
325 let mut snapshot_file = None;
326 let mut old_snapshot = None;
327 let mut pending_snapshots_path = None;
328 let is_doctest = is_doctest(function_name);
329
330 match new_snapshot_value {
331 SnapshotValue::FileText { name, .. } | SnapshotValue::Binary { name, .. } => {
332 let name = match &name {
333 Some(name) => add_suffix_to_snapshot_name(name.clone()),
334 None => {
335 if is_doctest {
336 panic!("Cannot determine reliable names for snapshot in doctests. Please use explicit names instead.");
337 }
338 detect_snapshot_name(function_name, module_path)
339 .unwrap()
340 .into()
341 }
342 };
343 if allow_duplicates() {
344 duplication_key = Some(format!("named:{module_path}|{name}"));
345 }
346 let file = get_snapshot_filename(
347 module_path,
348 assertion_file,
349 &name,
350 workspace,
351 is_doctest,
352 );
353 if fs::metadata(&file).is_ok() {
354 match Snapshot::from_file(&file) {
355 Ok(snapshot) => {
356 old_snapshot = Some(snapshot);
357 }
358 Err(err) => {
359 elog!(
363 "{}: Failed to parse snapshot file; \
364 a new snapshot will be generated: {}\n Error: {}",
365 style("warning").yellow().bold(),
366 file.display(),
367 err
368 );
369 }
370 }
371 }
372 snapshot_name = Some(name);
373 snapshot_file = Some(file);
374 }
375 SnapshotValue::InlineText {
376 reference_content: contents,
377 ..
378 } => {
379 if allow_duplicates() {
380 duplication_key = Some(format!(
381 "inline:{function_name}|{assertion_file}|{assertion_line}"
382 ));
383 } else {
384 prevent_inline_duplicate(function_name, assertion_file, assertion_line);
385 }
386 snapshot_name = detect_snapshot_name(function_name, module_path)
387 .ok()
388 .map(Cow::Owned);
389 let mut pending_file = workspace.join(assertion_file);
390 pending_file.set_file_name(format!(
391 ".{}.pending-snap",
392 pending_file
393 .file_name()
394 .expect("no filename")
395 .to_str()
396 .expect("non unicode filename")
397 ));
398 pending_snapshots_path = Some(pending_file);
399 old_snapshot = Some(Snapshot::from_components(
400 module_path.replace("::", "__"),
401 None,
402 MetaData::default(),
403 SnapshotContents::Text(TextSnapshotContents::from_inline_literal(contents)),
404 ));
405 }
406 };
407
408 let snapshot_type = match new_snapshot_value {
409 SnapshotValue::FileText { .. } | SnapshotValue::InlineText { .. } => SnapshotKind::Text,
410 &SnapshotValue::Binary { extension, .. } => SnapshotKind::Binary {
411 extension: extension.to_string(),
412 },
413 };
414
415 Ok(SnapshotAssertionContext {
416 tool_config,
417 workspace,
418 module_path,
419 snapshot_name,
420 snapshot_file,
421 old_snapshot,
422 pending_snapshots_path,
423 assertion_file,
424 assertion_line,
425 duplication_key,
426 is_doctest,
427 snapshot_kind: snapshot_type,
428 })
429 }
430
431 pub fn localize_path(&self, p: &Path) -> Option<PathBuf> {
433 let workspace = self.workspace.canonicalize().ok()?;
434 let p = self.workspace.join(p).canonicalize().ok()?;
435 p.strip_prefix(&workspace).ok().map(|x| x.to_path_buf())
436 }
437
438 pub fn new_snapshot(&self, contents: SnapshotContents, expr: &str) -> Snapshot {
440 assert_eq!(
441 contents.is_binary(),
442 matches!(self.snapshot_kind, SnapshotKind::Binary { .. })
443 );
444
445 Snapshot::from_components(
446 self.module_path.replace("::", "__"),
447 self.snapshot_name.as_ref().map(|x| x.to_string()),
448 Settings::with(|settings| MetaData {
449 source: {
450 let source_path = Path::new(self.assertion_file);
451 let canonicalized_base = self.workspace.canonicalize().ok();
456 let canonicalized_path = source_path.canonicalize().ok();
457
458 let relative = if let (Some(base), Some(path)) =
459 (canonicalized_base, canonicalized_path)
460 {
461 path_relative_from(&path, &base)
462 .unwrap_or_else(|| source_path.to_path_buf())
463 } else {
464 path_relative_from(source_path, self.workspace)
466 .unwrap_or_else(|| source_path.to_path_buf())
467 };
468 Some(path_to_storage(&relative))
469 },
470 assertion_line: Some(self.assertion_line),
471 description: settings.description().map(Into::into),
472 expression: if settings.omit_expression() {
473 None
474 } else {
475 Some(expr.to_string())
476 },
477 info: settings.info().map(ToOwned::to_owned),
478 input_file: settings
479 .input_file()
480 .and_then(|x| self.localize_path(x))
481 .map(|x| path_to_storage(&x)),
482 snapshot_kind: self.snapshot_kind.clone(),
483 }),
484 contents,
485 )
486 }
487
488 pub fn cleanup_passing(&self) -> Result<(), Box<dyn Error>> {
490 if let Some(ref snapshot_file) = self.snapshot_file {
493 let snapshot_file = snapshot_file.clone().with_extension("snap.new");
494 fs::remove_file(snapshot_file).ok();
495 }
496
497 if let Some(ref pending_snapshots) = self.pending_snapshots_path {
499 if fs::metadata(pending_snapshots).is_ok() {
500 PendingInlineSnapshot::new(None, None, self.assertion_line)
501 .save(pending_snapshots)?;
502 }
503 }
504 Ok(())
505 }
506
507 pub fn cleanup_previous_pending_binary_snapshots(&self) -> Result<(), Box<dyn Error>> {
511 if let Some(ref path) = self.snapshot_file {
512 let file_name_prefix = format!("{}.new.", path.file_name().unwrap().to_str().unwrap());
515
516 let read_dir = path.parent().unwrap().read_dir();
517
518 match read_dir {
519 Err(e) if e.kind() == ErrorKind::NotFound => return Ok(()),
520 _ => (),
521 }
522
523 for entry in read_dir? {
526 let entry = entry?;
527 let entry_file_name = entry.file_name();
528
529 if entry_file_name
532 .to_str()
533 .map(|f| f.starts_with(&file_name_prefix))
534 .unwrap_or(false)
535 {
536 std::fs::remove_file(entry.path())?;
537 }
538 }
539 }
540
541 Ok(())
542 }
543
544 pub fn update_snapshot(
546 &self,
547 new_snapshot: Snapshot,
548 ) -> Result<SnapshotUpdateBehavior, Box<dyn Error>> {
549 let unseen = self
552 .snapshot_file
553 .as_ref()
554 .map_or(false, |x| fs::metadata(x).is_ok());
555 let should_print = self.tool_config.output_behavior() != OutputBehavior::Nothing;
556 let snapshot_update = snapshot_update_behavior(&self.tool_config, unseen);
557
558 let snapshot_update =
562 if snapshot_update == SnapshotUpdateBehavior::InPlace && self.snapshot_file.is_none() {
564 SnapshotUpdateBehavior::NewFile
565 } else {
566 snapshot_update
567 };
568
569 match snapshot_update {
570 SnapshotUpdateBehavior::InPlace => {
571 if let Some(ref snapshot_file) = self.snapshot_file {
572 new_snapshot.save(snapshot_file)?;
573 if should_print {
574 elog!(
575 "{} {}",
576 style("updated snapshot").green(),
577 style(snapshot_file.display()).cyan().underlined(),
578 );
579 }
580 } else {
581 unreachable!()
583 }
584 }
585 SnapshotUpdateBehavior::NewFile => {
586 if let Some(ref snapshot_file) = self.snapshot_file {
587 let new_path = new_snapshot.save_new(snapshot_file)?;
589 if should_print {
590 elog!(
591 "{} {}",
592 style("stored new snapshot").green(),
593 style(new_path.display()).cyan().underlined(),
594 );
595 }
596 } else if self.is_doctest {
597 if should_print {
598 elog!(
599 "{}",
600 style("warning: cannot update inline snapshots in doctests")
601 .red()
602 .bold(),
603 );
604 }
605 } else {
606 PendingInlineSnapshot::new(
607 Some(new_snapshot),
608 self.old_snapshot.clone(),
609 self.assertion_line,
610 )
611 .save(self.pending_snapshots_path.as_ref().unwrap())?;
612 }
613 }
614 SnapshotUpdateBehavior::NoUpdate => {}
615 }
616
617 Ok(snapshot_update)
618 }
619
620 fn print_snapshot_info(&self, new_snapshot: &Snapshot) {
622 let mut printer =
623 SnapshotPrinter::new(self.workspace, self.old_snapshot.as_ref(), new_snapshot);
624 printer.set_line(Some(self.assertion_line));
625 printer.set_snapshot_file(self.snapshot_file.as_deref());
626 printer.set_title(Some("Snapshot Summary"));
627 printer.set_show_info(true);
628 match self.tool_config.output_behavior() {
629 OutputBehavior::Summary => {
630 printer.print();
631 }
632 OutputBehavior::Diff => {
633 printer.set_show_diff(true);
634 printer.print();
635 }
636 _ => {}
637 }
638 }
639
640 fn finalize(&self, update_result: SnapshotUpdateBehavior) {
643 let fail_fast = {
646 #[cfg(feature = "glob")]
647 {
648 if let Some(top) = crate::glob::GLOB_STACK.lock().unwrap().last() {
649 top.fail_fast
650 } else {
651 true
652 }
653 }
654 #[cfg(not(feature = "glob"))]
655 {
656 true
657 }
658 };
659
660 if fail_fast
661 && update_result == SnapshotUpdateBehavior::NewFile
662 && self.tool_config.output_behavior() != OutputBehavior::Nothing
663 && !self.is_doctest
664 {
665 println!(
666 "{hint}",
667 hint = style("To update snapshots run `cargo insta review`").dim(),
668 );
669 }
670
671 if update_result != SnapshotUpdateBehavior::InPlace && !self.tool_config.force_pass() {
672 if fail_fast && self.tool_config.output_behavior() != OutputBehavior::Nothing {
673 let msg = if env::var("INSTA_CARGO_INSTA") == Ok("1".to_string()) {
674 "Stopped on the first failure."
675 } else {
676 "Stopped on the first failure. Run `cargo insta test` to run all snapshots."
677 };
678 println!("{hint}", hint = style(msg).dim(),);
679 }
680
681 #[cfg(feature = "glob")]
685 {
686 let mut stack = crate::glob::GLOB_STACK.lock().unwrap();
687 if let Some(glob_collector) = stack.last_mut() {
688 glob_collector.failed += 1;
689 if update_result == SnapshotUpdateBehavior::NewFile
690 && self.tool_config.output_behavior() != OutputBehavior::Nothing
691 {
692 glob_collector.show_insta_hint = true;
693 }
694
695 print_or_panic!(
696 fail_fast,
697 "snapshot assertion from glob for '{}' failed in line {}",
698 self.snapshot_name.as_deref().unwrap_or("unnamed snapshot"),
699 self.assertion_line
700 );
701 return;
702 }
703 }
704
705 panic!(
706 "snapshot assertion for '{}' failed in line {}",
707 self.snapshot_name.as_deref().unwrap_or("unnamed snapshot"),
708 self.assertion_line
709 );
710 }
711 }
712}
713
714fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
725 use std::path::Component;
726
727 if path.is_absolute() != base.is_absolute() {
728 if path.is_absolute() {
729 Some(PathBuf::from(path))
730 } else {
731 None
732 }
733 } else {
734 let mut ita = path.components();
735 let mut itb = base.components();
736 let mut comps: Vec<Component> = vec![];
737 loop {
738 match (ita.next(), itb.next()) {
739 (None, None) => break,
740 (Some(a), None) => {
741 comps.push(a);
742 comps.extend(ita.by_ref());
743 break;
744 }
745 (None, _) => comps.push(Component::ParentDir),
746 (Some(a), Some(b)) if comps.is_empty() && a == b => {}
747 (Some(a), Some(_b)) => {
748 comps.push(Component::ParentDir);
749 for _ in itb {
750 comps.push(Component::ParentDir);
751 }
752 comps.push(a);
753 comps.extend(ita.by_ref());
754 break;
755 }
756 }
757 }
758 Some(comps.iter().map(|c| c.as_os_str()).collect())
759 }
760}
761
762fn prevent_inline_duplicate(function_name: &str, assertion_file: &str, assertion_line: u32) {
763 let key = format!("{function_name}|{assertion_file}|{assertion_line}");
764 let mut set = INLINE_DUPLICATES.lock().unwrap();
765 if set.contains(&key) {
766 drop(set);
768 panic!(
769 "Insta does not allow inline snapshot assertions in loops. \
770 Wrap your assertions in allow_duplicates! to change this."
771 );
772 }
773 set.insert(key);
774}
775
776fn record_snapshot_duplicate(
777 results: &mut BTreeMap<String, Snapshot>,
778 snapshot: &Snapshot,
779 ctx: &SnapshotAssertionContext,
780) {
781 let key = ctx.duplication_key.as_deref().unwrap();
782 if let Some(prev_snapshot) = results.get(key) {
783 if prev_snapshot.contents() != snapshot.contents() {
784 println!("Snapshots in allow-duplicates block do not match.");
785 let mut printer = SnapshotPrinter::new(ctx.workspace, Some(prev_snapshot), snapshot);
786 printer.set_line(Some(ctx.assertion_line));
787 printer.set_snapshot_file(ctx.snapshot_file.as_deref());
788 printer.set_title(Some("Differences in Block"));
789 printer.set_snapshot_hints("previous assertion", "current assertion");
790 if ctx.tool_config.output_behavior() == OutputBehavior::Diff {
791 printer.set_show_diff(true);
792 }
793 printer.print();
794 panic!(
795 "snapshot assertion for '{}' failed in line {}. Result \
796 does not match previous snapshot in allow-duplicates block.",
797 ctx.snapshot_name.as_deref().unwrap_or("unnamed snapshot"),
798 ctx.assertion_line
799 );
800 }
801 } else {
802 results.insert(key.to_string(), snapshot.clone());
803 }
804}
805
806fn allow_duplicates() -> bool {
808 RECORDED_DUPLICATES.with(|x| !x.borrow().is_empty())
809}
810
811pub fn with_allow_duplicates<R, F>(f: F) -> R
813where
814 F: FnOnce() -> R,
815{
816 RECORDED_DUPLICATES.with(|x| x.borrow_mut().push(BTreeMap::new()));
817 let rv = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
818 RECORDED_DUPLICATES.with(|x| x.borrow_mut().pop().unwrap());
819 match rv {
820 Ok(rv) => rv,
821 Err(payload) => std::panic::resume_unwind(payload),
822 }
823}
824
825#[allow(clippy::too_many_arguments)]
832pub fn assert_snapshot(
833 snapshot_value: SnapshotValue<'_>,
834 workspace: &Path,
835 function_name: &str,
836 module_path: &str,
837 assertion_file: &str,
838 assertion_line: u32,
839 expr: &str,
840) -> Result<(), Box<dyn Error>> {
841 let ctx = SnapshotAssertionContext::prepare(
842 &snapshot_value,
843 workspace,
844 function_name,
845 module_path,
846 assertion_file,
847 assertion_line,
848 )?;
849
850 ctx.cleanup_previous_pending_binary_snapshots()?;
851
852 let content = match snapshot_value {
853 SnapshotValue::FileText { content, .. } | SnapshotValue::InlineText { content, .. } => {
854 #[cfg(feature = "filters")]
856 let content = Settings::with(|settings| settings.filters().apply_to(content));
857
858 let kind = match ctx.snapshot_file {
859 Some(_) => TextSnapshotKind::File,
860 None => TextSnapshotKind::Inline,
861 };
862
863 TextSnapshotContents::new(content.into(), kind).into()
864 }
865 SnapshotValue::Binary {
866 content, extension, ..
867 } => {
868 assert!(
869 extension != "new",
870 "'.new' is not allowed as a file extension"
871 );
872 assert!(
873 !extension.starts_with("new."),
874 "file extensions starting with 'new.' are not allowed",
875 );
876
877 SnapshotContents::Binary(Rc::new(content))
878 }
879 };
880
881 let new_snapshot = ctx.new_snapshot(content, expr);
882
883 if let Some(ref snapshot_file) = ctx.snapshot_file {
885 memoize_snapshot_file(snapshot_file);
886 }
887
888 RECORDED_DUPLICATES.with(|x| {
892 if let Some(results) = x.borrow_mut().last_mut() {
893 record_snapshot_duplicate(results, &new_snapshot, &ctx);
894 }
895 });
896
897 let pass = ctx
898 .old_snapshot
899 .as_ref()
900 .map(|x| {
901 if ctx.tool_config.require_full_match() {
902 x.matches_fully(&new_snapshot)
903 } else {
904 x.matches(&new_snapshot)
905 }
906 })
907 .unwrap_or(false);
908
909 if pass {
910 ctx.cleanup_passing()?;
911
912 if matches!(
913 ctx.tool_config.snapshot_update(),
914 crate::env::SnapshotUpdate::Force
915 ) {
916 ctx.update_snapshot(new_snapshot)?;
917 }
918 } else {
920 ctx.print_snapshot_info(&new_snapshot);
921 let update_result = ctx.update_snapshot(new_snapshot)?;
922 ctx.finalize(update_result);
923 }
924
925 Ok(())
926}
927
928#[allow(rustdoc::private_doc_tests)]
929const _DOCTEST1: bool = false;