1mod action;
2mod error;
3
4#[cfg(feature = "color")]
5use anstream::panic;
6#[cfg(feature = "color")]
7use anstream::stderr;
8#[cfg(not(feature = "color"))]
9use std::io::stderr;
10
11use crate::IntoData;
12use crate::filter::{Filter as _, FilterNewlines, FilterPaths, NormalizeToExpected};
13
14pub use action::Action;
15pub use action::DEFAULT_ACTION_ENV;
16pub use error::Error;
17pub use error::Result;
18
19#[derive(Clone, Debug)]
32pub struct Assert {
33 pub(crate) action: Action,
34 action_var: Option<String>,
35 normalize_paths: bool,
36 substitutions: crate::Redactions,
37 pub(crate) palette: crate::report::Palette,
38}
39
40impl Assert {
42 pub fn new() -> Self {
43 Default::default()
44 }
45
46 #[track_caller]
76 pub fn eq(&self, actual: impl IntoData, expected: impl IntoData) {
77 let expected = expected.into_data();
78 let actual = actual.into_data();
79 if let Err(err) = self.try_eq(Some(&"In-memory"), actual, expected) {
80 err.panic();
81 }
82 }
83
84 #[track_caller]
85 #[deprecated(since = "0.6.0", note = "Replaced with `Assert::eq`")]
86 pub fn eq_(&self, actual: impl IntoData, expected: impl IntoData) {
87 self.eq(actual, expected);
88 }
89
90 pub fn try_eq(
91 &self,
92 actual_name: Option<&dyn std::fmt::Display>,
93 actual: crate::Data,
94 expected: crate::Data,
95 ) -> Result<()> {
96 if expected.source().is_none() && actual.source().is_some() {
97 panic!("received `(actual, expected)`, expected `(expected, actual)`");
98 }
99 match self.action {
100 Action::Skip => {
101 return Ok(());
102 }
103 Action::Ignore | Action::Verify | Action::Overwrite => {}
104 }
105
106 let (actual, expected) = self.normalize(actual, expected);
107
108 self.do_action(actual_name, actual, expected)
109 }
110
111 pub fn normalize(
112 &self,
113 mut actual: crate::Data,
114 mut expected: crate::Data,
115 ) -> (crate::Data, crate::Data) {
116 if expected.inner.filters.is_newlines_set() {
117 expected = FilterNewlines.filter(expected);
118 }
119
120 actual = actual.coerce_to(expected.against_format());
122 actual = actual.coerce_to(expected.intended_format());
123
124 if self.normalize_paths && expected.inner.filters.is_paths_set() {
125 actual = FilterPaths.filter(actual);
126 }
127 if expected.inner.filters.is_newlines_set() {
128 actual = FilterNewlines.filter(actual);
129 }
130
131 let mut normalize = NormalizeToExpected::new();
132 if expected.inner.filters.is_redaction_set() {
133 normalize = normalize.redact_with(&self.substitutions);
134 }
135 if expected.inner.filters.is_unordered_set() {
136 normalize = normalize.unordered();
137 }
138 actual = normalize.normalize(actual, &expected);
139
140 (actual, expected)
141 }
142
143 fn do_action(
144 &self,
145 actual_name: Option<&dyn std::fmt::Display>,
146 actual: crate::Data,
147 expected: crate::Data,
148 ) -> Result<()> {
149 let result = self.try_verify(actual_name, &actual, &expected);
150 let Err(err) = result else {
151 return Ok(());
152 };
153 match self.action {
154 Action::Skip => unreachable!("Bailed out earlier"),
155 Action::Ignore => {
156 use std::io::Write;
157
158 let _ = writeln!(
159 stderr(),
160 "{}: {}",
161 self.palette.warn("Ignoring failure"),
162 err
163 );
164 Ok(())
165 }
166 Action::Verify => {
167 let message = if expected.source().is_none() {
168 crate::report::Styled::new(String::new(), Default::default())
169 } else if let Some(action_var) = self.action_var.as_deref() {
170 self.palette
171 .hint(format!("Update with {action_var}=overwrite"))
172 } else {
173 crate::report::Styled::new(String::new(), Default::default())
174 };
175 Err(Error::new(format_args!("{err}{message}")))
176 }
177 Action::Overwrite => {
178 use std::io::Write;
179
180 if let Some(source) = expected.source() {
181 if let Err(message) = actual.write_to(source) {
182 Err(Error::new(format_args!("{err}Update failed: {message}")))
183 } else {
184 let _ = writeln!(stderr(), "{}: {}", self.palette.warn("Fixing"), err);
185 Ok(())
186 }
187 } else {
188 Err(Error::new(format_args!("{err}")))
189 }
190 }
191 }
192 }
193
194 fn try_verify(
195 &self,
196 actual_name: Option<&dyn std::fmt::Display>,
197 actual: &crate::Data,
198 expected: &crate::Data,
199 ) -> Result<()> {
200 if actual != expected {
201 let mut buf = String::new();
202 crate::report::write_diff(
203 &mut buf,
204 expected,
205 actual,
206 expected.source().map(|s| s as &dyn std::fmt::Display),
207 actual_name,
208 self.palette,
209 )
210 .map_err(|e| e.to_string())?;
211 Err(buf.into())
212 } else {
213 Ok(())
214 }
215 }
216}
217
218#[cfg(feature = "dir")]
220impl Assert {
221 #[track_caller]
222 pub fn subset_eq(
223 &self,
224 expected_root: impl Into<std::path::PathBuf>,
225 actual_root: impl Into<std::path::PathBuf>,
226 ) {
227 let expected_root = expected_root.into();
228 let actual_root = actual_root.into();
229 self.subset_eq_inner(expected_root, actual_root);
230 }
231
232 #[track_caller]
233 fn subset_eq_inner(&self, expected_root: std::path::PathBuf, actual_root: std::path::PathBuf) {
234 match self.action {
235 Action::Skip => {
236 return;
237 }
238 Action::Ignore | Action::Verify | Action::Overwrite => {}
239 }
240
241 let checks: Vec<_> =
242 crate::dir::PathDiff::subset_eq_iter_inner(expected_root, actual_root).collect();
243 self.verify(checks);
244 }
245
246 #[track_caller]
247 pub fn subset_matches(
248 &self,
249 pattern_root: impl Into<std::path::PathBuf>,
250 actual_root: impl Into<std::path::PathBuf>,
251 ) {
252 let pattern_root = pattern_root.into();
253 let actual_root = actual_root.into();
254 self.subset_matches_inner(pattern_root, actual_root);
255 }
256
257 #[track_caller]
258 fn subset_matches_inner(
259 &self,
260 expected_root: std::path::PathBuf,
261 actual_root: std::path::PathBuf,
262 ) {
263 match self.action {
264 Action::Skip => {
265 return;
266 }
267 Action::Ignore | Action::Verify | Action::Overwrite => {}
268 }
269
270 let checks: Vec<_> = crate::dir::PathDiff::subset_matches_iter_inner(
271 expected_root,
272 actual_root,
273 &self.substitutions,
274 self.normalize_paths,
275 )
276 .collect();
277 self.verify(checks);
278 }
279
280 #[track_caller]
281 fn verify(
282 &self,
283 mut checks: Vec<Result<(std::path::PathBuf, std::path::PathBuf), crate::dir::PathDiff>>,
284 ) {
285 if checks.iter().all(Result::is_ok) {
286 for check in checks {
287 let (_expected_path, _actual_path) = check.unwrap();
288 crate::debug!(
289 "{}: is {}",
290 _expected_path.display(),
291 self.palette.info("good")
292 );
293 }
294 } else {
295 checks.sort_by_key(|c| match c {
296 Ok((expected_path, _actual_path)) => Some(expected_path.clone()),
297 Err(diff) => diff.expected_path().map(|p| p.to_owned()),
298 });
299
300 let mut buffer = String::new();
301 let mut ok = true;
302 for check in checks {
303 use std::fmt::Write;
304 match check {
305 Ok((expected_path, _actual_path)) => {
306 let _ = writeln!(
307 &mut buffer,
308 "{}: is {}",
309 expected_path.display(),
310 self.palette.info("good"),
311 );
312 }
313 Err(diff) => {
314 let _ = diff.write(&mut buffer, self.palette);
315 match self.action {
316 Action::Skip => unreachable!("Bailed out earlier"),
317 Action::Ignore | Action::Verify => {
318 ok = false;
319 }
320 Action::Overwrite => {
321 if let Err(err) = diff.overwrite() {
322 ok = false;
323 let path = diff
324 .expected_path()
325 .expect("always present when overwrite can fail");
326 let _ = writeln!(
327 &mut buffer,
328 "{} to overwrite {}: {}",
329 self.palette.error("Failed"),
330 path.display(),
331 err
332 );
333 }
334 }
335 }
336 }
337 }
338 }
339 if ok {
340 use std::io::Write;
341 let _ = write!(stderr(), "{buffer}");
342 match self.action {
343 Action::Skip => unreachable!("Bailed out earlier"),
344 Action::Ignore => {
345 let _ =
346 write!(stderr(), "{}", self.palette.warn("Ignoring above failures"));
347 }
348 Action::Verify => unreachable!("Something had to fail to get here"),
349 Action::Overwrite => {
350 let _ = write!(
351 stderr(),
352 "{}",
353 self.palette.warn("Overwrote above failures")
354 );
355 }
356 }
357 } else {
358 match self.action {
359 Action::Skip => unreachable!("Bailed out earlier"),
360 Action::Ignore => unreachable!("Shouldn't be able to fail"),
361 Action::Verify => {
362 use std::fmt::Write;
363 if let Some(action_var) = self.action_var.as_deref() {
364 writeln!(
365 &mut buffer,
366 "{}",
367 self.palette
368 .hint(format_args!("Update with {action_var}=overwrite"))
369 )
370 .unwrap();
371 }
372 }
373 Action::Overwrite => {}
374 }
375 panic!("{}", buffer);
376 }
377 }
378 }
379}
380
381impl Assert {
383 pub fn palette(mut self, palette: crate::report::Palette) -> Self {
385 self.palette = palette;
386 self
387 }
388
389 pub fn action_env(mut self, var_name: &str) -> Self {
391 let action = Action::with_env_var(var_name);
392 self.action = action.unwrap_or(self.action);
393 self.action_var = Some(var_name.to_owned());
394 self
395 }
396
397 pub fn action(mut self, action: Action) -> Self {
399 self.action = action;
400 self.action_var = None;
401 self
402 }
403
404 pub fn redact_with(mut self, substitutions: crate::Redactions) -> Self {
406 self.substitutions = substitutions;
407 self
408 }
409
410 #[deprecated(since = "0.6.2", note = "Replaced with `Assert::redact_with`")]
412 pub fn substitutions(self, substitutions: crate::Redactions) -> Self {
413 self.redact_with(substitutions)
414 }
415
416 pub fn normalize_paths(mut self, yes: bool) -> Self {
420 self.normalize_paths = yes;
421 self
422 }
423}
424
425impl Assert {
426 pub fn selected_action(&self) -> Action {
427 self.action
428 }
429
430 pub fn redactions(&self) -> &crate::Redactions {
431 &self.substitutions
432 }
433}
434
435impl Default for Assert {
436 fn default() -> Self {
437 Self {
438 action: Default::default(),
439 action_var: Default::default(),
440 normalize_paths: true,
441 substitutions: Default::default(),
442 palette: crate::report::Palette::color(),
443 }
444 .redact_with(crate::Redactions::with_exe())
445 }
446}