• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

calder / rust-goldenfile / 18608315895

18 Oct 2025 12:50AM UTC coverage: 87.379% (-0.6%) from 88.0%
18608315895

push

github

web-flow
Merge pull request #13 from calder/dev/invalid-utf8

Fall back to binary diff for non-UTF8.

7 of 7 new or added lines in 2 files covered. (100.0%)

1 existing line in 1 file now uncovered.

90 of 103 relevant lines covered (87.38%)

2.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.12
/src/mint.rs
1
//! Used to create goldenfiles.
2

3
use std::env;
4
use std::fs;
5
use std::fs::File;
6
use std::io::{Error, ErrorKind, Result};
7
use std::path::{Path, PathBuf};
8
use std::thread;
9

10
use tempfile::TempDir;
11
use yansi::Paint;
12

13
use crate::differs::*;
14

15
/// A Mint creates goldenfiles.
16
///
17
/// When a Mint goes out of scope, it will do one of two things depending on the
18
/// value of the `UPDATE_GOLDENFILES` environment variable:
19
///
20
///   1. If `UPDATE_GOLDENFILES!=1`, it will check the new goldenfile
21
///      contents against their old contents, and panic if they differ.
22
///   2. If `UPDATE_GOLDENFILES=1`, it will replace the old goldenfile
23
///      contents with the newly written contents.
24
pub struct Mint {
25
    path: PathBuf,
26
    tempdir: TempDir,
27
    files: Vec<(PathBuf, Differ)>,
28
    create_empty: bool,
29
}
30

31
impl Mint {
32
    /// Create a new goldenfile Mint.
33
    fn new_internal<P: AsRef<Path>>(path: P, create_empty: bool) -> Self {
1✔
34
        let tempdir = TempDir::new().unwrap();
4✔
35
        let mint = Mint {
36
            path: path.as_ref().to_path_buf(),
4✔
37
            files: vec![],
3✔
38
            tempdir,
39
            create_empty,
40
        };
41
        fs::create_dir_all(&mint.path).unwrap_or_else(|err| {
4✔
42
            panic!(
×
43
                "Failed to create goldenfile directory {:?}: {:?}",
×
44
                mint.path, err
×
45
            )
46
        });
47

48
        mint
1✔
49
    }
50

51
    /// Create a new goldenfile Mint.
52
    pub fn new<P: AsRef<Path>>(path: P) -> Self {
4✔
53
        Self::new_internal(path, true)
3✔
54
    }
55

56
    /// Create a new goldenfile Mint. Goldenfiles will only be created when non-empty.
57
    pub fn new_nonempty<P: AsRef<Path>>(path: P) -> Self {
1✔
58
        Self::new_internal(path, false)
1✔
59
    }
60

61
    /// Create a new goldenfile using a differ inferred from the file extension.
62
    ///
63
    /// The returned File is a temporary file, not the goldenfile itself.
64
    pub fn new_goldenfile<P: AsRef<Path>>(&mut self, path: P) -> Result<File> {
3✔
65
        self.new_goldenfile_with_differ(&path, get_differ_for_path(&path))
2✔
66
    }
67

68
    /// Create a new goldenfile with the specified diff function.
69
    ///
70
    /// The returned File is a temporary file, not the goldenfile itself.
71
    pub fn new_goldenfile_with_differ<P: AsRef<Path>>(
1✔
72
        &mut self,
73
        path: P,
74
        differ: Differ,
75
    ) -> Result<File> {
76
        let abs_path = self.new_goldenpath_with_differ(path, differ)?;
5✔
77
        if let Some(abs_parent) = abs_path.parent() {
3✔
78
            if abs_parent != self.tempdir.path() {
3✔
79
                fs::create_dir_all(abs_parent).unwrap_or_else(|err| {
1✔
80
                    panic!(
×
81
                        "Failed to create temporary subdirectory {:?}: {:?}",
×
82
                        abs_parent, err
×
83
                    )
84
                });
85
            }
86
        }
87

88
        let maybe_file = File::create(abs_path);
1✔
89
        if maybe_file.is_err() {
3✔
90
            self.files.pop();
×
91
        }
92

93
        maybe_file
2✔
94
    }
95

96
    /// Check new goldenfile contents against old, and panic if they differ.
97
    ///
98
    /// Called automatically when a Mint goes out of scope and
99
    /// `UPDATE_GOLDENFILES!=1`.
100
    pub fn check_goldenfiles(&self) {
3✔
101
        for (file, differ) in &self.files {
3✔
102
            let old = self.path.join(file);
1✔
103
            let new = self.tempdir.path().join(file);
3✔
104
            defer_on_unwind! {
3✔
105
                eprintln!("note: run with `UPDATE_GOLDENFILES=1` to update goldenfiles");
1✔
106
                eprintln!(
1✔
107
                    "{}: goldenfile changed: {}",
108
                    "error".bold().red(),
1✔
109
                    file.to_str().unwrap()
1✔
110
                );
111
            }
112
            differ(&old, &new);
3✔
113
        }
114
    }
115

116
    /// Overwrite old goldenfile contents with their new contents.
117
    ///
118
    /// Called automatically when a Mint goes out of scope and
119
    /// `UPDATE_GOLDENFILES=1`.
120
    pub fn update_goldenfiles(&self) {
1✔
121
        for (file, _) in &self.files {
2✔
122
            let old = self.path.join(file);
1✔
123
            let new = self.tempdir.path().join(file);
2✔
124

125
            let empty = File::open(&new).unwrap().metadata().unwrap().len() == 0;
3✔
126
            if self.create_empty || !empty {
1✔
127
                println!("Updating {:?}.", file.to_str().unwrap());
2✔
128
                fs::copy(&new, &old).unwrap_or_else(|err| {
1✔
129
                    panic!("Error copying {:?} to {:?}: {:?}", &new, &old, err)
×
130
                });
131
            } else if old.exists() {
×
132
                std::fs::remove_file(&old).unwrap();
×
133
            }
134
        }
135
    }
136

137
    /// Register a new goldenfile using a differ inferred from the file extension.
138
    ///
139
    /// The returned PathBuf references a temporary file, not the goldenfile itself.
140
    pub fn new_goldenpath<P: AsRef<Path>>(&mut self, path: P) -> Result<PathBuf> {
1✔
141
        self.new_goldenpath_with_differ(&path, get_differ_for_path(&path))
2✔
142
    }
143

144
    /// Register a new goldenfile with the specified diff function.
145
    ///
146
    /// The returned PathBuf references a temporary file, not the goldenfile itself.
147
    pub fn new_goldenpath_with_differ<P: AsRef<Path>>(
1✔
148
        &mut self,
149
        path: P,
150
        differ: Differ,
151
    ) -> Result<PathBuf> {
152
        if !path.as_ref().is_relative() {
4✔
153
            return Err(Error::new(
2✔
UNCOV
154
                ErrorKind::InvalidInput,
×
155
                "Path must be relative.",
×
156
            ));
157
        }
158

159
        let abs_path = self.tempdir.path().to_path_buf().join(path.as_ref());
3✔
160
        self.files.push((path.as_ref().to_path_buf(), differ));
2✔
161

162
        Ok(abs_path)
1✔
163
    }
164
}
165

166
/// Get the diff function to use for a given file path.
167
pub fn get_differ_for_path<P: AsRef<Path>>(_path: P) -> Differ {
3✔
168
    match _path.as_ref().extension() {
5✔
169
        Some(os_str) => match os_str.to_str() {
5✔
170
            Some("bin") => Box::new(binary_diff),
4✔
171
            Some("exe") => Box::new(binary_diff),
2✔
172
            Some("gz") => Box::new(binary_diff),
2✔
173
            Some("pcap") => Box::new(binary_diff),
2✔
174
            Some("tar") => Box::new(binary_diff),
2✔
175
            Some("zip") => Box::new(binary_diff),
2✔
176
            _ => Box::new(text_diff),
1✔
177
        },
178
        _ => Box::new(text_diff),
1✔
179
    }
180
}
181

182
impl Drop for Mint {
183
    /// Called when the mint goes out of scope to check or update goldenfiles.
184
    fn drop(&mut self) {
2✔
185
        if thread::panicking() {
1✔
186
            return;
187
        }
188
        // For backwards compatibility with 1.4 and below.
189
        let legacy_var = env::var("REGENERATE_GOLDENFILES");
1✔
190
        let update_var = env::var("UPDATE_GOLDENFILES");
2✔
191
        if (legacy_var.is_ok() && legacy_var.unwrap() == "1")
3✔
192
            || (update_var.is_ok() && update_var.unwrap() == "1")
3✔
193
        {
194
            self.update_goldenfiles();
×
195
        } else {
196
            self.check_goldenfiles();
2✔
197
        }
198
    }
199
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc