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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
//! # Snapshot testing toolbox
//!
//! > When you have to treat your tests like pets, instead of [cattle][trycmd]
//!
//! `snapbox` is a snapshot-testing toolbox that is ready to use for verifying output from
//! - Function return values
//! - CLI stdout/stderr
//! - Filesystem changes
//!
//! It is also flexible enough to build your own test harness like [trycmd](https://crates.io/crates/trycmd).
//!
//! ## Which tool is right
//!
//! - [cram](https://bitheap.org/cram/): End-to-end CLI snapshotting agnostic of any programming language
//! - [trycmd](https://crates.io/crates/trycmd): For running a lot of blunt tests (limited test predicates)
//! - Particular attention is given to allow the test data to be pulled into documentation, like
//! with [mdbook](https://rust-lang.github.io/mdBook/)
//! - `snapbox`: When you want something like `trycmd` in one off
//! cases or you need to customize `trycmd`s behavior.
//! - [assert_cmd](https://crates.io/crates/assert_cmd) +
//! [assert_fs](https://crates.io/crates/assert_fs): Test cases follow a certain pattern but
//! special attention is needed in how to verify the results.
//! - Hand-written test cases: for peculiar circumstances
//!
//! ## Getting Started
//!
//! Testing Functions:
//! - [`assert_eq`][crate::assert_eq] and [`assert_matches`] for reusing diffing / pattern matching for non-snapshot testing
//! - [`assert_eq_path`][crate::assert_eq_path] and [`assert_matches_path`] for one-off assertions with the snapshot stored in a file
//! - [`harness::Harness`] for discovering test inputs and asserting against snapshot files:
//!
//! Testing Commands:
//! - [`cmd::Command`]: Process spawning for testing of non-interactive commands
//! - [`cmd::OutputAssert`]: Assert the state of a [`Command`][cmd::Command]'s
//! [`Output`][std::process::Output].
//!
//! Testing Filesystem Interactions:
//! - [`path::PathFixture`]: Working directory for tests
//! - [`Assert`]: Diff a directory against files present in a pattern directory
//!
//! You can also build your own version of these with the lower-level building blocks these are
//! made of.
//!
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
//!
//! # Examples
//!
//! [`assert_matches`]
//! ```rust
//! snapbox::assert_matches("Hello [..] people!", "Hello many people!");
//! ```
//!
//! [`Assert`]
//! ```rust,no_run
//! let actual = "...";
//! let expected_path = "tests/fixtures/help_output_is_clean.txt";
//! snapbox::Assert::new()
//! .action_env("SNAPSHOTS")
//! .matches_path(expected_path, actual);
//! ```
//!
//! [`harness::Harness`]
#![cfg_attr(not(feature = "harness"), doc = " ```rust,ignore")]
#![cfg_attr(feature = "harness", doc = " ```rust,no_run")]
//! snapbox::harness::Harness::new(
//! "tests/fixtures/invalid",
//! setup,
//! test,
//! )
//! .select(["tests/cases/*.in"])
//! .action_env("SNAPSHOTS")
//! .test();
//!
//! fn setup(input_path: std::path::PathBuf) -> snapbox::harness::Case {
//! let name = input_path.file_name().unwrap().to_str().unwrap().to_owned();
//! let expected = input_path.with_extension("out");
//! snapbox::harness::Case {
//! name,
//! fixture: input_path,
//! expected,
//! }
//! }
//!
//! fn test(input_path: &std::path::Path) -> Result<usize, Box<dyn std::error::Error>> {
//! let raw = std::fs::read_to_string(input_path)?;
//! let num = raw.parse::<usize>()?;
//!
//! let actual = num + 10;
//!
//! Ok(actual)
//! }
//! ```
//!
//! [trycmd]: https://docs.rs/trycmd
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
mod action;
mod assert;
mod data;
mod error;
mod substitutions;
pub mod cmd;
pub mod path;
pub mod report;
pub mod utils;
#[cfg(feature = "harness")]
pub mod harness;
pub use action::Action;
pub use action::DEFAULT_ACTION_ENV;
pub use assert::Assert;
pub use data::Data;
pub use data::DataFormat;
pub use data::{Normalize, NormalizeMatches, NormalizeNewlines, NormalizePaths};
pub use error::Error;
pub use snapbox_macros::debug;
pub use substitutions::Substitutions;
pub type Result<T, E = Error> = std::result::Result<T, E>;
/// Check if a value is the same as an expected value
///
/// When the content is text, newlines are normalized.
///
/// ```rust
/// let output = "something";
/// let expected = "something";
/// snapbox::assert_matches(expected, output);
/// ```
#[track_caller]
pub fn assert_eq(expected: impl Into<crate::Data>, actual: impl Into<crate::Data>) {
Assert::new().eq(expected, actual);
}
/// Check if a value matches a pattern
///
/// Pattern syntax:
/// - `...` is a line-wildcard when on a line by itself
/// - `[..]` is a character-wildcard when inside a line
/// - `[EXE]` matches `.exe` on Windows
///
/// Normalization:
/// - Newlines
/// - `\` to `/`
///
/// ```rust
/// let output = "something";
/// let expected = "so[..]g";
/// snapbox::assert_matches(expected, output);
/// ```
#[track_caller]
pub fn assert_matches(pattern: impl Into<crate::Data>, actual: impl Into<crate::Data>) {
Assert::new().matches(pattern, actual);
}
/// Check if a value matches the content of a file
///
/// When the content is text, newlines are normalized.
///
/// ```rust,no_run
/// let output = "something";
/// let expected_path = "tests/snapshots/output.txt";
/// snapbox::assert_eq_path(expected_path, output);
/// ```
#[track_caller]
pub fn assert_eq_path(expected_path: impl AsRef<std::path::Path>, actual: impl Into<crate::Data>) {
Assert::new()
.action_env(DEFAULT_ACTION_ENV)
.eq_path(expected_path, actual);
}
/// Check if a value matches the pattern in a file
///
/// Pattern syntax:
/// - `...` is a line-wildcard when on a line by itself
/// - `[..]` is a character-wildcard when inside a line
/// - `[EXE]` matches `.exe` on Windows
///
/// Normalization:
/// - Newlines
/// - `\` to `/`
///
/// ```rust,no_run
/// let output = "something";
/// let expected_path = "tests/snapshots/output.txt";
/// snapbox::assert_matches_path(expected_path, output);
/// ```
#[track_caller]
pub fn assert_matches_path(
pattern_path: impl AsRef<std::path::Path>,
actual: impl Into<crate::Data>,
) {
Assert::new()
.action_env(DEFAULT_ACTION_ENV)
.matches_path(pattern_path, actual);
}
/// Check if a path matches the content of another path, recursively
///
/// When the content is text, newlines are normalized.
///
/// ```rust,no_run
/// let output_root = "...";
/// let expected_root = "tests/snapshots/output.txt";
/// snapbox::assert_subset_eq(expected_root, output_root);
/// ```
#[cfg(feature = "path")]
#[track_caller]
pub fn assert_subset_eq(
expected_root: impl Into<std::path::PathBuf>,
actual_root: impl Into<std::path::PathBuf>,
) {
Assert::new()
.action_env(DEFAULT_ACTION_ENV)
.subset_eq(expected_root, actual_root);
}
/// Check if a path matches the pattern of another path, recursively
///
/// Pattern syntax:
/// - `...` is a line-wildcard when on a line by itself
/// - `[..]` is a character-wildcard when inside a line
/// - `[EXE]` matches `.exe` on Windows
///
/// Normalization:
/// - Newlines
/// - `\` to `/`
///
/// ```rust,no_run
/// let output_root = "...";
/// let expected_root = "tests/snapshots/output.txt";
/// snapbox::assert_subset_matches(expected_root, output_root);
/// ```
#[cfg(feature = "path")]
#[track_caller]
pub fn assert_subset_matches(
pattern_root: impl Into<std::path::PathBuf>,
actual_root: impl Into<std::path::PathBuf>,
) {
Assert::new()
.action_env(DEFAULT_ACTION_ENV)
.subset_matches(pattern_root, actual_root);
}