1use std::path::{Path, PathBuf};
15
16use pretty_assertions::assert_str_eq;
17
18#[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 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 pub fn assert(&self, got: &str) {
52 let want;
53 if is_update_golden() {
54 want = got;
55 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
68fn read_as_utf8(filename: &Path) -> std::io::Result<String> {
71 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}