1#![cfg_attr(docsrs, feature(doc_cfg))]
10#![warn(clippy::print_stderr)]
11#![warn(clippy::print_stdout)]
12
13pub use toml_test::DecodedScalar;
14pub use toml_test::DecodedValue;
15pub use toml_test::Decoder;
16pub use toml_test::Encoder;
17pub use toml_test::Error;
18
19pub struct DecoderHarness<D> {
51 decoder: D,
52 matches: Option<Matches>,
53 version: Option<String>,
54 custom_valid: Vec<toml_test_data::Valid<'static>>,
55 custom_invalid: Vec<toml_test_data::Invalid<'static>>,
56 #[cfg(feature = "snapshot")]
57 snapshot_root: Option<std::path::PathBuf>,
58}
59
60impl<D> DecoderHarness<D>
61where
62 D: Decoder + Copy + Send + Sync + 'static,
63{
64 pub fn new(decoder: D) -> Self {
65 Self {
66 decoder,
67 matches: None,
68 version: None,
69 custom_valid: Vec::new(),
70 custom_invalid: Vec::new(),
71 #[cfg(feature = "snapshot")]
72 snapshot_root: None,
73 }
74 }
75
76 pub fn ignore<'p>(
77 &mut self,
78 patterns: impl IntoIterator<Item = &'p str>,
79 ) -> Result<&mut Self, Error> {
80 self.matches = Some(Matches::new(patterns.into_iter())?);
81 Ok(self)
82 }
83
84 pub fn version(&mut self, version: impl Into<String>) -> &mut Self {
85 self.version = Some(version.into());
86 self
87 }
88
89 pub fn extend_valid(
90 &mut self,
91 cases: impl IntoIterator<Item = toml_test_data::Valid<'static>>,
92 ) -> &mut Self {
93 self.custom_valid.extend(cases);
94 self
95 }
96
97 pub fn extend_invalid(
98 &mut self,
99 cases: impl IntoIterator<Item = toml_test_data::Invalid<'static>>,
100 ) -> &mut Self {
101 self.custom_invalid.extend(cases);
102 self
103 }
104
105 #[cfg(feature = "snapshot")]
106 pub fn snapshot_root(&mut self, root: impl Into<std::path::PathBuf>) -> &mut Self {
107 self.snapshot_root = Some(root.into());
108 self
109 }
110
111 pub fn test(self) -> ! {
112 let harness = libtest2_mimic::Harness::with_env();
113
114 let versioned = self
115 .version
116 .as_deref()
117 .into_iter()
118 .flat_map(toml_test_data::version)
119 .collect::<std::collections::HashSet<_>>();
120
121 let mut tests = Vec::new();
122 let decoder = self.decoder;
123 #[cfg(feature = "snapshot")]
124 let snapshot_root = self.snapshot_root;
125 tests.extend(
126 toml_test_data::valid()
127 .map(|case| {
128 let ignore = !versioned.contains(case.name());
129 (case, ignore)
130 })
131 .chain(self.custom_valid.into_iter().map(|c| (c, false)))
132 .map(|(case, mut ignore)| {
133 ignore |= self
134 .matches
135 .as_ref()
136 .map(|m| !m.matched(case.name()))
137 .unwrap_or_default();
138 (case, ignore)
139 })
140 .map(move |(case, ignore)| {
141 libtest2_mimic::Trial::test(case.name().display().to_string(), move |context| {
142 if ignore {
143 context.ignore()?;
144 }
145 decoder
146 .verify_valid_case(case.fixture(), case.expected())
147 .map_err(libtest2_mimic::RunError::fail)
148 })
149 }),
150 );
151 tests.extend(
152 toml_test_data::invalid()
153 .map(|case| {
154 let ignore = !versioned.contains(case.name());
155 (case, ignore)
156 })
157 .chain(self.custom_invalid.into_iter().map(|c| (c, false)))
158 .map(|(case, mut ignore)| {
159 ignore |= self
160 .matches
161 .as_ref()
162 .map(|m| !m.matched(case.name()))
163 .unwrap_or_default();
164 (case, ignore)
165 })
166 .map(move |(case, ignore)| {
167 #[cfg(feature = "snapshot")]
168 let snapshot_root = snapshot_root.clone();
169 libtest2_mimic::Trial::test(case.name().display().to_string(), move |context| {
170 if ignore {
171 context.ignore()?;
172 }
173 match decoder.verify_invalid_case(case.fixture()) {
174 Ok(_err) => {
175 #[cfg(feature = "snapshot")]
176 if let Some(snapshot_root) = snapshot_root.as_deref() {
177 let snapshot_path =
178 snapshot_root.join(case.name().with_extension("stderr"));
179 snapbox::assert_data_eq!(
180 _err.to_string(),
181 snapbox::Data::read_from(&snapshot_path, None).raw()
182 );
183 }
184 Ok(())
185 }
186 Err(err) => Err(libtest2_mimic::RunError::fail(err)),
187 }
188 })
189 }),
190 );
191 harness.discover(tests).main()
192 }
193}
194
195pub struct EncoderHarness<E, D> {
242 encoder: E,
243 fixture: D,
244 matches: Option<Matches>,
245 version: Option<String>,
246 custom_valid: Vec<toml_test_data::Valid<'static>>,
247}
248
249impl<E, D> EncoderHarness<E, D>
250where
251 E: Encoder + Copy + Send + Sync + 'static,
252 D: Decoder + Copy + Send + Sync + 'static,
253{
254 pub fn new(encoder: E, fixture: D) -> Self {
255 Self {
256 encoder,
257 fixture,
258 matches: None,
259 version: None,
260 custom_valid: Vec::new(),
261 }
262 }
263
264 pub fn ignore<'p>(
265 &mut self,
266 patterns: impl IntoIterator<Item = &'p str>,
267 ) -> Result<&mut Self, Error> {
268 self.matches = Some(Matches::new(patterns.into_iter())?);
269 Ok(self)
270 }
271
272 pub fn version(&mut self, version: impl Into<String>) -> &mut Self {
273 self.version = Some(version.into());
274 self
275 }
276
277 pub fn extend_valid(
278 &mut self,
279 cases: impl IntoIterator<Item = toml_test_data::Valid<'static>>,
280 ) -> &mut Self {
281 self.custom_valid.extend(cases);
282 self
283 }
284
285 pub fn test(self) -> ! {
286 let harness = libtest2_mimic::Harness::with_env();
287
288 let versioned = self
289 .version
290 .as_deref()
291 .into_iter()
292 .flat_map(toml_test_data::version)
293 .collect::<std::collections::HashSet<_>>();
294
295 let mut tests = Vec::new();
296 let encoder = self.encoder;
297 let fixture = self.fixture;
298 tests.extend(
299 toml_test_data::valid()
300 .map(|case| {
301 let ignore = !versioned.contains(case.name());
302 (case, ignore)
303 })
304 .chain(self.custom_valid.into_iter().map(|c| (c, false)))
305 .map(|(case, mut ignore)| {
306 ignore |= self
307 .matches
308 .as_ref()
309 .map(|m| !m.matched(case.name()))
310 .unwrap_or_default();
311 (case, ignore)
312 })
313 .map(move |(case, ignore)| {
314 libtest2_mimic::Trial::test(case.name().display().to_string(), move |context| {
315 if ignore {
316 context.ignore()?;
317 }
318 encoder
319 .verify_valid_case(case.expected(), &fixture)
320 .map_err(libtest2_mimic::RunError::fail)
321 })
322 }),
323 );
324 harness.discover(tests).main()
325 }
326}
327
328struct Matches {
329 ignores: ignore::gitignore::Gitignore,
330}
331
332impl Matches {
333 fn new<'p>(patterns: impl Iterator<Item = &'p str>) -> Result<Self, Error> {
334 let mut ignores = ignore::gitignore::GitignoreBuilder::new(".");
335 for line in patterns {
336 ignores.add_line(None, line).map_err(Error::new)?;
337 }
338 let ignores = ignores.build().map_err(Error::new)?;
339 Ok(Self { ignores })
340 }
341
342 fn matched(&self, path: &std::path::Path) -> bool {
343 match self.ignores.matched_path_or_any_parents(path, false) {
344 ignore::Match::None | ignore::Match::Whitelist(_) => true,
345 ignore::Match::Ignore(_) => false,
346 }
347 }
348}
349
350#[doc = include_str!("../README.md")]
351#[cfg(doctest)]
352pub struct ReadmeDoctests;