Skip to main content

okane_golden/
lib.rs

1//! Simple golden testing framework.
2//!
3//! You can simply create a golden file by calling [`Golden::new`].
4//!
5//! ```
6//! let target = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("testdata/mukai.txt");
7//! let golden = okane_golden::Golden::new(target)?;
8//! golden.assert("zazen boys\n");
9//! # Ok::<(), std::io::Error>(())
10//! ```
11//!
12//! If the test fails, simply pass `UPDATE_GOLDEN=1` env var to rerun the test.
13
14use std::path::{Path, PathBuf};
15
16use pretty_assertions::assert_str_eq;
17
18/// Golden object to maintain the expected content.
19#[derive(Debug)]
20pub struct Golden {
21    path: PathBuf,
22    content: String,
23}
24
25fn is_update_golden() -> bool {
26    !std::env::var("UPDATE_GOLDEN")
27        .unwrap_or_default()
28        .is_empty()
29}
30
31impl Golden {
32    /// Returns a new instance.
33    /// Note returned instance ignores CR/CRLF difference.
34    pub fn new(path: PathBuf) -> Result<Self, std::io::Error> {
35        let content = read_as_utf8(&path).or_else(|e| {
36            if e.kind() == std::io::ErrorKind::NotFound {
37                if is_update_golden() {
38                    Ok(String::new())
39                } else {
40                    Err(std::io::Error::new(e.kind(), format!("Golden file {} not found: pass the environment variable UPDATE_GOLDEN=1 to write golden file", path.display())))
41                }
42            } else {
43                Err(e)
44            }
45        })?;
46        Ok(Self { path, content })
47    }
48
49    /// Assert the given `got` str against the golden file.
50    /// Pass `UPDATE_GOLDEN` env to update the golden itself.
51    pub fn assert(&self, got: &str) {
52        let want;
53        if is_update_golden() {
54            want = got;
55            // update the original_file.
56            std::fs::write(&self.path, got).expect("Update golden failed");
57        } else {
58            want = &self.content;
59        }
60        assert_str_eq!(
61            want,
62            got,
63            "comparison against golden failed. Pass UPDATE_GOLDEN=1 to update the golden."
64        );
65    }
66}
67
68/// Returns content of testdata directory, as UTF-8.
69/// This fucntion replaces CRLF to LF.
70fn read_as_utf8(filename: &Path) -> std::io::Result<String> {
71    // Needs to replace CRLF to LF for Windows, as all files may have CRLF
72    // depending on the git config core.autocrlf.
73    std::fs::read_to_string(filename).map(|s| s.replace("\r\n", "\n"))
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    use regex::Regex;
81
82    fn testdata_dir() -> PathBuf {
83        Path::new(env!("CARGO_MANIFEST_DIR")).join("testdata/")
84    }
85
86    #[test]
87    fn new_fails_on_non_existing_file() {
88        temp_env::with_var_unset("UPDATE_GOLDEN", || {
89            let path = testdata_dir().join("non_existing_golden.txt");
90            let got_err = Golden::new(path).expect_err("this must fail");
91
92            assert!(Regex::new("Golden file .* not found")
93                .unwrap()
94                .is_match(&got_err.to_string()));
95        });
96    }
97
98    #[test]
99    fn assert_succeeds_on_correct_golden() {
100        temp_env::with_var_unset("UPDATE_GOLDEN", || {
101            let path = testdata_dir().join("mukai.txt");
102
103            let golden = Golden::new(path).unwrap();
104            golden.assert("zazen boys\n");
105        });
106    }
107
108    #[test]
109    fn assert_fails_on_different_content() {
110        temp_env::with_var_unset("UPDATE_GOLDEN", || {
111            let path = testdata_dir().join("mukai.txt");
112
113            let golden = Golden::new(path).unwrap();
114
115            let got_err = std::panic::catch_unwind(|| golden.assert("number girl"))
116                .expect_err("this assertion must fail");
117
118            let payload = &*got_err;
119            if payload.is::<String>() {
120                assert!(payload
121                    .downcast_ref::<String>()
122                    .unwrap()
123                    .contains("assertion failed"));
124            } else {
125                panic!("unexpected type of assertion failure");
126            }
127        });
128    }
129}