goldenfile-1.5.2/.cargo_vcs_info.json0000644000000001360000000000100131510ustar { "git": { "sha1": "6d1d5edb5e5f4b2fe0b5a1b6710fca5e1f9c35ff" }, "path_in_vcs": "" }goldenfile-1.5.2/.github/workflows/test.yaml000064400000000000000000000015771046102023000172130ustar 00000000000000# TODO: Test README. name: test run-name: Test on: [push] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - run: cargo fmt -- --check test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - run: cargo test --all-features coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - run: curl -L https://github.com/xd009642/tarpaulin/releases/download/0.26.1/cargo-tarpaulin-x86_64-unknown-linux-gnu.tar.gz | tar xzf - && mv cargo-tarpaulin ~/.cargo/bin - run: cargo tarpaulin --all-features --out=xml - uses: coverallsapp/github-action@v2 - uses: codecov/codecov-action@v1.0.2 with: token: ${{ secrets.CODECOV_TOKEN }} goldenfile-1.5.2/.gitignore000064400000000000000000000000431046102023000137260ustar 00000000000000/Cargo.lock /cobertura.xml /target goldenfile-1.5.2/Cargo.toml0000644000000017150000000000100111530ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "goldenfile" version = "1.5.2" authors = ["Calder Coalson "] exclude = [ "/.travis.yml", "/scripts", ] description = "Simple goldenfile testing library" documentation = "https://docs.rs/goldenfile" readme = "README.md" keywords = [ "goldenfile", "test", "library", ] license = "MIT" repository = "https://github.com/calder/rust-goldenfile" [dependencies.similar-asserts] version = "1.4" [dependencies.tempfile] version = "3" goldenfile-1.5.2/Cargo.toml.orig000064400000000000000000000007101046102023000146260ustar 00000000000000[package] name = "goldenfile" version = "1.5.2" edition = "2021" description = "Simple goldenfile testing library" keywords = ["goldenfile", "test", "library"] documentation = "https://docs.rs/goldenfile" repository = "https://github.com/calder/rust-goldenfile" readme = "README.md" license = "MIT" authors = ["Calder Coalson "] exclude = [ "/.travis.yml", "/scripts", ] [dependencies] similar-asserts = "1.4" tempfile = "3" goldenfile-1.5.2/LICENSE000064400000000000000000000020571046102023000127520ustar 00000000000000Copyright (c) 2016 The Rust Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. goldenfile-1.5.2/README.md000064400000000000000000000035061046102023000132240ustar 00000000000000# 👑 Rust Goldenfile [![Documentation](https://docs.rs/goldenfile/badge.svg)](https://docs.rs/goldenfile) [![Latest Version](https://img.shields.io/crates/v/goldenfile.svg)](https://crates.io/crates/goldenfile) [![Build Status](https://github.com/calder/rust-goldenfile/actions/workflows/test.yaml/badge.svg)](https://github.com/calder/rust-goldenfile/actions/workflows/test.yaml?query=branch%3Amain) [![Coverage Status](https://codecov.io/github/calder/rust-goldenfile/branch/main/graph/badge.svg?token=ROF7q6w5no)](https://app.codecov.io/gh/calder/rust-goldenfile) **Simple goldenfile testing in Rust.** [Goldenfile](https://softwareengineering.stackexchange.com/questions/358786/what-is-golden-files) tests generate one or more output files as they run. If any files differ from their checked-in "golden" version, the test fails. This ensures that behavioral changes are intentional, explicit, and version controlled. You can use goldenfiles to test the output of a parser, the order of a graph traversal, the result of a simulation, or anything else that should only change with human review. ## Usage ```rust extern crate goldenfile; use std::io::Write; use goldenfile::Mint; #[test] fn test() { let mut mint = Mint::new("tests/goldenfiles"); let mut file1 = mint.new_goldenfile("file1.txt").unwrap(); let mut file2 = mint.new_goldenfile("file2.txt").unwrap(); write!(file1, "Hello world!").unwrap(); write!(file2, "Foo bar!").unwrap(); } ``` When the `Mint` goes out of scope, it compares the contents of each file to its checked-in golden version and fails the test if they differ. To update the checked-in versions, run: ```sh UPDATE_GOLDENFILES=1 cargo test ``` ## Contributing Pull requests are welcome! This project follows the Rust community's [Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). goldenfile-1.5.2/src/differs.rs000064400000000000000000000034411046102023000145220ustar 00000000000000//! Functions for comparing files. use std::fs::{metadata, File}; use std::io; use std::io::{BufReader, Read}; use std::path::Path; use similar_asserts; /// A function that displays a diff and panics if two files to not match. pub type Differ = Box; /// Compare unicode text files. Print a colored diff and panic on failure. pub fn text_diff(old: &Path, new: &Path) { similar_asserts::assert_eq!(&read_file(old), &read_file(new)); } /// Panic if binary files differ with some basic information about where they /// differ. pub fn binary_diff(old: &Path, new: &Path) { let old_len = file_len(old); let new_len = file_len(new); if old_len != new_len { panic!( "File sizes differ: Old file is {} bytes, new file is {} bytes", old_len, new_len ); } let first_difference = file_byte_iter(old) .zip(file_byte_iter(new)) .position(|(old_byte, new_byte)| old_byte != new_byte); if let Some(position) = first_difference { panic!("Files differ at byte {}", position + 1); } } fn open_file(path: &Path) -> File { check_io(File::open(path), "opening file", path) } fn file_byte_iter(path: &Path) -> impl Iterator + '_ { BufReader::new(open_file(path)) .bytes() .map(move |b| check_io(b, "reading file", path)) } fn file_len(path: &Path) -> u64 { check_io(metadata(path), "getting file length", path).len() } fn read_file(path: &Path) -> String { let mut contents = String::new(); check_io( open_file(path).read_to_string(&mut contents), "reading file", path, ); contents } fn check_io(x: Result, message: &str, path: &Path) -> T { x.unwrap_or_else(|err| panic!("Error {} {:?}: {:?}", message, path, err)) } goldenfile-1.5.2/src/lib.rs000064400000000000000000000016241046102023000136470ustar 00000000000000//! Goldenfile tests generate one or more output files as they run. If any files //! differ from their checked-in "golden" version, the test fails. This ensures //! that behavioral changes are intentional, explicit, and version controlled. //! //! # Example //! //! ```rust //! #[test] //! fn test() { //! let mut mint = Mint::new("tests/goldenfiles"); //! let mut file1 = mint.new_goldenfile("file1.txt").unwrap(); //! let mut file2 = mint.new_goldenfile("file2.txt").unwrap(); //! //! write!(file1, "Hello world!").unwrap(); //! write!(file2, "Foo bar!").unwrap(); //! } //! ``` //! //! When the `Mint` goes out of scope, it compares the contents of each file //! to its checked-in golden version and fails the test if they differ. To //! update the checked-in versions, run: //! ```sh //! UPDATE_GOLDENFILES=1 cargo test //! ``` #![deny(missing_docs)] pub mod differs; pub mod mint; pub use mint::*; goldenfile-1.5.2/src/mint.rs000064400000000000000000000122241046102023000140460ustar 00000000000000//! Used to create goldenfiles. use std::env; use std::fs; use std::fs::File; use std::io::{Error, ErrorKind, Result}; use std::path::{Path, PathBuf}; use std::thread; use tempfile::TempDir; use crate::differs::*; /// A Mint creates goldenfiles. /// /// When a Mint goes out of scope, it will do one of two things depending on the /// value of the `UPDATE_GOLDENFILES` environment variable: /// /// 1. If `UPDATE_GOLDENFILES!=1`, it will check the new goldenfile /// contents against their old contents, and panic if they differ. /// 2. If `UPDATE_GOLDENFILES=1`, it will replace the old goldenfile /// contents with the newly written contents. pub struct Mint { path: PathBuf, tempdir: TempDir, files: Vec<(PathBuf, Differ)>, } impl Mint { /// Create a new goldenfile Mint. /// /// All goldenfiles will be created in the Mint's directory. pub fn new>(path: P) -> Self { let tempdir = TempDir::new().unwrap(); let mint = Mint { path: path.as_ref().to_path_buf(), files: vec![], tempdir, }; fs::create_dir_all(&mint.path).unwrap_or_else(|err| { panic!( "Failed to create goldenfile directory {:?}: {:?}", mint.path, err ) }); mint } /// Create a new goldenfile using a differ inferred from the file extension. /// /// The returned File is a temporary file, not the goldenfile itself. pub fn new_goldenfile>(&mut self, path: P) -> Result { self.new_goldenfile_with_differ(&path, get_differ_for_path(&path)) } /// Create a new goldenfile with the specified diff function. /// /// The returned File is a temporary file, not the goldenfile itself. pub fn new_goldenfile_with_differ>( &mut self, path: P, differ: Differ, ) -> Result { if path.as_ref().is_absolute() { return Err(Error::new( ErrorKind::InvalidInput, "Path must be relative.", )); } let abs_path = self.tempdir.path().to_path_buf().join(path.as_ref()); if let Some(abs_parent) = abs_path.parent() { if abs_parent != self.tempdir.path() { fs::create_dir_all(&abs_parent).unwrap_or_else(|err| { panic!( "Failed to create temporary subdirectory {:?}: {:?}", abs_parent, err ) }); } } let maybe_file = File::create(abs_path); if maybe_file.is_ok() { self.files.push((path.as_ref().to_path_buf(), differ)); } maybe_file } /// Check new goldenfile contents against old, and panic if they differ. /// /// Called automatically when a Mint goes out of scope and /// `UPDATE_GOLDENFILES!=1`. pub fn check_goldenfiles(&self) { for &(ref file, ref differ) in &self.files { let old = self.path.join(&file); let new = self.tempdir.path().join(&file); println!("\nGoldenfile diff for {:?}:", file.to_str().unwrap()); println!("To regenerate the goldenfile, run"); println!(" UPDATE_GOLDENFILES=1 cargo test"); println!("------------------------------------------------------------"); differ(&old, &new); println!(""); } } /// Overwrite old goldenfile contents with their new contents. /// /// Called automatically when a Mint goes out of scope and /// `UPDATE_GOLDENFILES=1`. pub fn update_goldenfiles(&self) { for &(ref file, _) in &self.files { let old = self.path.join(&file); let new = self.tempdir.path().join(&file); println!("Updating {:?}.", file.to_str().unwrap()); fs::copy(&new, &old) .unwrap_or_else(|err| panic!("Error copying {:?} to {:?}: {:?}", &new, &old, err)); } } } /// Get the diff function to use for a given file path. pub fn get_differ_for_path>(_path: P) -> Differ { match _path.as_ref().extension() { Some(os_str) => match os_str.to_str() { Some("bin") => Box::new(binary_diff), Some("exe") => Box::new(binary_diff), Some("gz") => Box::new(binary_diff), Some("tar") => Box::new(binary_diff), Some("zip") => Box::new(binary_diff), _ => Box::new(text_diff), }, _ => Box::new(text_diff), } } impl Drop for Mint { /// Called when the mint goes out of scope to check or update goldenfiles. fn drop(&mut self) { if thread::panicking() { return; } // For backwards compatibility with 1.4 and below. let legacy_var = env::var("REGENERATE_GOLDENFILES"); let update_var = env::var("UPDATE_GOLDENFILES"); if (legacy_var.is_ok() && legacy_var.unwrap() == "1") || (update_var.is_ok() && update_var.unwrap() == "1") { self.update_goldenfiles(); } else { self.check_goldenfiles(); } } } goldenfile-1.5.2/tests/.gitignore000064400000000000000000000000001046102023000150610ustar 00000000000000goldenfile-1.5.2/tests/goldenfiles/binary_content_diff.bin000064400000000000000000000000031046102023000220670ustar 00000000000000goldenfile-1.5.2/tests/goldenfiles/binary_match1.bin000064400000000000000000000000001046102023000205770ustar 00000000000000goldenfile-1.5.2/tests/goldenfiles/binary_match2.bin000064400000000000000000000000031046102023000206030ustar 00000000000000goldenfile-1.5.2/tests/goldenfiles/binary_size_diff.bin000064400000000000000000000000021046102023000213660ustar 00000000000000goldenfile-1.5.2/tests/goldenfiles/file1.txt000064400000000000000000000000141046102023000171320ustar 00000000000000Hello world!goldenfile-1.5.2/tests/goldenfiles/file2.txt000064400000000000000000000000101046102023000171270ustar 00000000000000Foo bar!goldenfile-1.5.2/tests/goldenfiles/match1.txt000064400000000000000000000000141046102023000173070ustar 00000000000000Hello world!goldenfile-1.5.2/tests/goldenfiles/match2.txt000064400000000000000000000000061046102023000173110ustar 00000000000000foobargoldenfile-1.5.2/tests/goldenfiles/panic.txt000064400000000000000000000000031046102023000172220ustar 00000000000000oldgoldenfile-1.5.2/tests/goldenfiles/subdir/file1.txt000064400000000000000000000000161046102023000204240ustar 00000000000000File in subdirgoldenfile-1.5.2/tests/goldenfiles/text_diff1.txt000064400000000000000000000000141046102023000201670ustar 00000000000000Hello world!goldenfile-1.5.2/tests/goldenfiles/text_diff2.txt000064400000000000000000000000061046102023000201710ustar 00000000000000foobargoldenfile-1.5.2/tests/goldenfiles/update1.txt000064400000000000000000000000141046102023000174750ustar 00000000000000Hello world!goldenfile-1.5.2/tests/goldenfiles/update2.txt000064400000000000000000000000061046102023000174770ustar 00000000000000foobargoldenfile-1.5.2/tests/goldenfiles/update_env1.txt000064400000000000000000000000141046102023000203450ustar 00000000000000Hello world!goldenfile-1.5.2/tests/goldenfiles/update_env2.txt000064400000000000000000000000061046102023000203470ustar 00000000000000foobargoldenfile-1.5.2/tests/readme_usage.rs000064400000000000000000000005421046102023000160730ustar 00000000000000extern crate goldenfile; use std::io::Write; use goldenfile::Mint; #[test] fn test() { let mut mint = Mint::new("tests/goldenfiles"); let mut file1 = mint.new_goldenfile("file1.txt").unwrap(); let mut file2 = mint.new_goldenfile("file2.txt").unwrap(); write!(file1, "Hello world!").unwrap(); write!(file2, "Foo bar!").unwrap(); } goldenfile-1.5.2/tests/test.rs000064400000000000000000000051511046102023000144320ustar 00000000000000extern crate goldenfile; use std::fs; use std::io::Write; use goldenfile::Mint; #[test] fn binary_match() { let mut mint = Mint::new("tests/goldenfiles"); let mut file1 = mint.new_goldenfile("binary_match1.bin").unwrap(); let mut file2 = mint.new_goldenfile("binary_match2.bin").unwrap(); file1.write_all(b"").unwrap(); file2.write_all(b"\x00\x01\x02").unwrap(); } #[test] fn subdir() { let mut mint = Mint::new("tests/goldenfiles"); let mut file1 = mint.new_goldenfile("subdir/file1.txt").unwrap(); write!(file1, "File in subdir").unwrap(); } #[test] #[should_panic(expected = "File sizes differ: Old file is 2 bytes, new file is 3 bytes")] fn binary_size_diff() { let mut mint = Mint::new("tests/goldenfiles"); let mut file = mint.new_goldenfile("binary_size_diff.bin").unwrap(); file.write_all(b"\x00\x01\x02").unwrap(); } #[test] #[should_panic(expected = "Files differ at byte 3")] fn binary_content_diff() { let mut mint = Mint::new("tests/goldenfiles"); let mut file = mint.new_goldenfile("binary_content_diff.bin").unwrap(); file.write_all(b"\x00\x01\x02").unwrap(); } #[test] fn text_match() { let mut mint = Mint::new("tests/goldenfiles"); let mut file1 = mint.new_goldenfile("match1.txt").unwrap(); let mut file2 = mint.new_goldenfile("match2.txt").unwrap(); write!(file1, "Hello world!").unwrap(); write!(file2, "foobar").unwrap(); } #[test] #[should_panic(expected = "foobar")] fn text_diff() { let mut mint = Mint::new("tests/goldenfiles"); let mut file1 = mint.new_goldenfile("text_diff1.txt").unwrap(); let mut file2 = mint.new_goldenfile("text_diff2.txt").unwrap(); write!(file1, "Hello world!").unwrap(); write!(file2, "monkeybrains").unwrap(); } #[test] #[should_panic(expected = "Path must be relative")] fn absolute_path() { let mut mint = Mint::new("tests/goldenfiles"); mint.new_goldenfile("/bar").unwrap(); } #[test] #[should_panic(expected = "assertion failed")] fn external_panic() { let mut mint = Mint::new("tests/goldenfiles"); let mut file1 = mint.new_goldenfile("panic.txt").unwrap(); write!(file1, "new").unwrap(); assert!(false); } #[test] fn update() { fs::remove_file("tests/goldenfiles/update_env1.txt").unwrap(); fs::remove_file("tests/goldenfiles/update_env2.txt").unwrap(); let mut mint = Mint::new("tests/goldenfiles"); let mut file1 = mint.new_goldenfile("update_env1.txt").unwrap(); let mut file2 = mint.new_goldenfile("update_env2.txt").unwrap(); write!(file1, "Hello world!").unwrap(); write!(file2, "foobar").unwrap(); mint.update_goldenfiles() }