testdata_rt/
snapshots.rs

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}