1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// Copyright 2020 Oxide Computer Company

//! This library is for comparing multi-line output to data stored in version
//! controlled files. It makes it easy to update the contents when should be
//! updated to match the new results.
//!
//! Use it like this:
//!
//! ```rust
//! # fn compose() -> &'static str { "" }
//! let actual: &str = compose();
//! expectorate::assert_contents("lyrics.txt", actual);
//! ```
//!
//! If the output doesn't match, the program will panic! and emit the
//! color-coded diffs.
//!
//! To accept the changes from `compose()`, run with ! `EXPECTORATE=overwrite`.
//! Assuming `lyrics.txt` is checked in, `git diff` will show you something like
//! this:
//!
//! ```diff
//! diff --git a/examples/lyrics.txt b/examples/lyrics.txt
//! index e4104c1..ea6beaf 100644
//! --- a/examples/lyrics.txt
//! +++ b/examples/lyrics.txt
//! @@ -1,5 +1,2 @@
//! -No one hits like Gaston
//! -Matches wits like Gaston
//! -In a spitting match nobody spits like Gaston
//! +In a testing match nobody tests like Gaston
//! I'm especially good at expectorating
//! -Ten points for Gaston
//! ```

use difference::Changeset;
use newline_converter::dos2unix;
use std::{env, fs, path::Path};

/// Compare the contents of the file to the string provided
#[track_caller]
pub fn assert_contents<P: AsRef<Path>>(path: P, actual: &str) {
    let path = path.as_ref();
    let var = env::var_os("EXPECTORATE");
    let overwrite = match var.as_ref().map(|s| s.as_os_str().to_str()) {
        Some(Some("overwrite")) => true,
        _ => false,
    };

    let actual = dos2unix(actual);

    if overwrite {
        if let Err(e) = fs::write(path, actual.as_ref()) {
            panic!("unable to write to {}: {}", path.display(), e.to_string());
        }
    } else {
        // Treat non-existant files like an empty file.
        let expected_s = match fs::read_to_string(path) {
            Ok(s) => s,
            Err(e) => match e.kind() {
                std::io::ErrorKind::NotFound => String::new(),
                _ => panic!(
                    "unable to read contents of {}: {}",
                    path.display(),
                    e.to_string()
                ),
            },
        };
        let expected = dos2unix(&expected_s);

        let changeset =
            Changeset::new(expected.as_ref(), actual.as_ref(), "\n");
        if changeset.distance != 0 {
            println!("{}", changeset);
            panic!(
                r#"assertion failed: string doesn't match the contents of file: "{}" see diffset above
                set EXPECTORATE=overwrite if these changes are intentional"#,
                path.display()
            );
        }
    }
}