1use std::borrow::Borrow;
2use std::env;
3
4use crate::fixtures::TestFile;
5use crate::test_input::TestInput;
6
7#[macro_export]
8macro_rules! assert_snapshot {
9 ($e:expr, snapshot = $test_file:expr) => {
10 match (&($e), &($test_file)) {
11 (e, test_file) => $crate::assert_snapshot_helper(e, test_file, |lhs, rhs| {
12 $crate::pretty_assertions::assert_eq!(*lhs, *rhs)
13 }),
14 }
15 };
16}
17
18pub fn assert_snapshot_helper<T, F>(e: &T, test_file: &TestFile, assertion: F)
19where
20 T: Snapshot + ?Sized,
21 T::Borrowed: PartialEq,
22 F: FnOnce(&T::Borrowed, &T::Borrowed),
23{
24 let mode = SnapshotMode::current();
25 let expected = if let Some(expected) = test_file.raw_read_opt() {
26 expected
27 } else if mode >= SnapshotMode::New {
28 write_snapshot(e, test_file);
29 return;
30 } else {
31 panic!(
32 "Snapshot does not exist: {}",
33 test_file.path_for_writing().display()
34 );
35 };
36
37 let expected = T::Owned::read_from(&expected);
38 if *e.borrow() != *expected.borrow() {
39 if mode == SnapshotMode::All {
40 write_snapshot(e, test_file);
41 return;
42 }
43 assertion(e.borrow(), expected.borrow());
44 unreachable!();
45 }
46}
47
48fn write_snapshot<T>(e: &T, fixture: &TestFile)
49where
50 T: Snapshot + ?Sized,
51{
52 let bytes = e.to_bytes();
53 fixture.raw_write(&bytes);
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub enum SnapshotMode {
58 None,
59 New,
60 All,
61}
62
63impl SnapshotMode {
64 pub fn current() -> SnapshotMode {
65 let update_snapshots = env::var("UPDATE_SNAPSHOTS").unwrap_or_else(|_| String::from(""));
66 let ci = env::var("CI").unwrap_or_else(|_| String::from(""));
67 if update_snapshots == "all" || update_snapshots == "true" || update_snapshots == "1" {
68 return SnapshotMode::All;
69 } else if update_snapshots == "new" {
70 return SnapshotMode::New;
71 } else if update_snapshots == "none"
72 || update_snapshots == "false"
73 || update_snapshots == "0"
74 {
75 return SnapshotMode::None;
76 }
77 if ci == "true" || ci == "1" {
78 return SnapshotMode::None;
79 }
80 SnapshotMode::New
81 }
82}
83
84pub trait Snapshot {
85 type Borrowed: ?Sized;
86 type Owned: Borrow<Self::Borrowed> + TestInput;
87
88 fn borrow(&self) -> &Self::Borrowed;
89 fn to_bytes(&self) -> Vec<u8>;
90}
91
92impl<'a, T> Snapshot for &'a T
93where
94 T: Snapshot + ?Sized,
95{
96 type Borrowed = T::Borrowed;
97 type Owned = T::Owned;
98
99 fn borrow(&self) -> &Self::Borrowed {
100 <T as Snapshot>::borrow(self)
101 }
102
103 fn to_bytes(&self) -> Vec<u8> {
104 <T as Snapshot>::to_bytes(self)
105 }
106}
107
108impl<'a, T> Snapshot for &'a mut T
109where
110 T: Snapshot + ?Sized,
111{
112 type Borrowed = T::Borrowed;
113 type Owned = T::Owned;
114
115 fn borrow(&self) -> &Self::Borrowed {
116 <T as Snapshot>::borrow(self)
117 }
118
119 fn to_bytes(&self) -> Vec<u8> {
120 <T as Snapshot>::to_bytes(self)
121 }
122}
123
124impl Snapshot for [u8] {
125 type Borrowed = [u8];
126 type Owned = Vec<u8>;
127
128 fn borrow(&self) -> &Self::Borrowed {
129 self
130 }
131
132 fn to_bytes(&self) -> Vec<u8> {
133 self.to_owned()
134 }
135}
136
137impl Snapshot for Vec<u8> {
138 type Borrowed = Vec<u8>;
139 type Owned = Vec<u8>;
140
141 fn borrow(&self) -> &Self::Borrowed {
142 self
143 }
144
145 fn to_bytes(&self) -> Vec<u8> {
146 self.clone()
147 }
148}
149
150impl Snapshot for str {
151 type Borrowed = str;
152 type Owned = String;
153
154 fn borrow(&self) -> &Self::Borrowed {
155 self
156 }
157
158 fn to_bytes(&self) -> Vec<u8> {
159 self.as_bytes().to_owned()
160 }
161}
162
163impl Snapshot for String {
164 type Borrowed = String;
165 type Owned = String;
166
167 fn borrow(&self) -> &Self::Borrowed {
168 self
169 }
170
171 fn to_bytes(&self) -> Vec<u8> {
172 self.as_bytes().to_owned()
173 }
174}