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