1 // Copyright 2015-2016 Brian Smith.
2 //
3 // Permission to use, copy, modify, and/or distribute this software for any
4 // purpose with or without fee is hereby granted, provided that the above
5 // copyright notice and this permission notice appear in all copies.
6 //
7 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
10 // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15 //! Testing framework.
16 //!
17 //! Unlike the rest of *ring*, this testing framework uses panics pretty
18 //! liberally. It was originally designed for internal use--it drives most of
19 //! *ring*'s internal tests, and so it is optimized for getting *ring*'s tests
20 //! written quickly at the expense of some usability. The documentation is
21 //! lacking. The best way to learn it is to look at some examples. The digest
22 //! tests are the most complicated because they use named sections. Other tests
23 //! avoid named sections and so are easier to understand.
24 //!
25 //! # Examples
26 //!
27 //! ## Writing Tests
28 //!
29 //! Input files look like this:
30 //!
31 //! ```text
32 //! # This is a comment.
33 //!
34 //! HMAC = SHA1
35 //! Input = "My test data"
36 //! Key = ""
37 //! Output = 61afdecb95429ef494d61fdee15990cabf0826fc
38 //!
39 //! HMAC = SHA256
40 //! Input = "Sample message for keylen<blocklen"
41 //! Key = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
42 //! Output = A28CF43130EE696A98F14A37678B56BCFCBDD9E5CF69717FECF5480F0EBDF790
43 //! ```
44 //!
45 //! Test cases are separated with blank lines. Note how the bytes of the `Key`
46 //! attribute are specified as a quoted string in the first test case and as
47 //! hex in the second test case; you can use whichever form is more convenient
48 //! and you can mix and match within the same file. The empty sequence of bytes
49 //! can only be represented with the quoted string form (`""`).
50 //!
51 //! Here's how you would consume the test data:
52 //!
53 //! ```ignore
54 //! use ring::test;
55 //!
56 //! test::run(test::test_file!("hmac_tests.txt"), |section, test_case| {
57 //! assert_eq!(section, ""); // This test doesn't use named sections.
58 //!
59 //! let digest_alg = test_case.consume_digest_alg("HMAC");
60 //! let input = test_case.consume_bytes("Input");
61 //! let key = test_case.consume_bytes("Key");
62 //! let output = test_case.consume_bytes("Output");
63 //!
64 //! // Do the actual testing here
65 //! });
66 //! ```
67 //!
68 //! Note that `consume_digest_alg` automatically maps the string "SHA1" to a
69 //! reference to `digest::SHA1_FOR_LEGACY_USE_ONLY`, "SHA256" to
70 //! `digest::SHA256`, etc.
71 //!
72 //! ## Output When a Test Fails
73 //!
74 //! When a test case fails, the framework automatically prints out the test
75 //! case. If the test case failed with a panic, then the backtrace of the panic
76 //! will be printed too. For example, let's say the failing test case looks
77 //! like this:
78 //!
79 //! ```text
80 //! Curve = P-256
81 //! a = 2b11cb945c8cf152ffa4c9c2b1c965b019b35d0b7626919ef0ae6cb9d232f8af
82 //! b = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c
83 //! r = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c
84 //! ```
85 //! If the test fails, this will be printed (if `$RUST_BACKTRACE` is `1`):
86 //!
87 //! ```text
88 //! src/example_tests.txt: Test panicked.
89 //! Curve = P-256
90 //! a = 2b11cb945c8cf152ffa4c9c2b1c965b019b35d0b7626919ef0ae6cb9d232f8af
91 //! b = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c
92 //! r = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c
93 //! thread 'example_test' panicked at 'Test failed.', src\test.rs:206
94 //! stack backtrace:
95 //! 0: 0x7ff654a05c7c - std::rt::lang_start::h61f4934e780b4dfc
96 //! 1: 0x7ff654a04f32 - std::rt::lang_start::h61f4934e780b4dfc
97 //! 2: 0x7ff6549f505d - std::panicking::rust_panic_with_hook::hfe203e3083c2b544
98 //! 3: 0x7ff654a0825b - rust_begin_unwind
99 //! 4: 0x7ff6549f63af - std::panicking::begin_panic_fmt::h484cd47786497f03
100 //! 5: 0x7ff654a07e9b - rust_begin_unwind
101 //! 6: 0x7ff654a0ae95 - core::panicking::panic_fmt::h257ceb0aa351d801
102 //! 7: 0x7ff654a0b190 - core::panicking::panic::h4bb1497076d04ab9
103 //! 8: 0x7ff65496dc41 - from_file<closure>
104 //! at C:\Users\Example\example\<core macros>:4
105 //! 9: 0x7ff65496d49c - example_test
106 //! at C:\Users\Example\example\src\example.rs:652
107 //! 10: 0x7ff6549d192a - test::stats::Summary::new::ha139494ed2e4e01f
108 //! 11: 0x7ff6549d51a2 - test::stats::Summary::new::ha139494ed2e4e01f
109 //! 12: 0x7ff654a0a911 - _rust_maybe_catch_panic
110 //! 13: 0x7ff6549d56dd - test::stats::Summary::new::ha139494ed2e4e01f
111 //! 14: 0x7ff654a03783 - std::sys::thread::Thread::new::h2b08da6cd2517f79
112 //! 15: 0x7ff968518101 - BaseThreadInitThunk
113 //! ```
114 //!
115 //! Notice that the output shows the name of the data file
116 //! (`src/example_tests.txt`), the test inputs that led to the failure, and the
117 //! stack trace to the line in the test code that panicked: entry 9 in the
118 //! stack trace pointing to line 652 of the file `example.rs`.
119
120 #[cfg(feature = "alloc")]
121 use alloc::{format, string::String, vec::Vec};
122
123 #[cfg(feature = "alloc")]
124 use crate::{bits, digest, error};
125
126 #[cfg(any(feature = "std", feature = "test_logging"))]
127 extern crate std;
128
129 /// `compile_time_assert_clone::<T>();` fails to compile if `T` doesn't
130 /// implement `Clone`.
compile_time_assert_clone<T: Clone>()131 pub fn compile_time_assert_clone<T: Clone>() {}
132
133 /// `compile_time_assert_copy::<T>();` fails to compile if `T` doesn't
134 /// implement `Copy`.
compile_time_assert_copy<T: Copy>()135 pub fn compile_time_assert_copy<T: Copy>() {}
136
137 /// `compile_time_assert_send::<T>();` fails to compile if `T` doesn't
138 /// implement `Send`.
compile_time_assert_send<T: Send>()139 pub fn compile_time_assert_send<T: Send>() {}
140
141 /// `compile_time_assert_sync::<T>();` fails to compile if `T` doesn't
142 /// implement `Sync`.
compile_time_assert_sync<T: Sync>()143 pub fn compile_time_assert_sync<T: Sync>() {}
144
145 /// `compile_time_assert_std_error_error::<T>();` fails to compile if `T`
146 /// doesn't implement `std::error::Error`.
147 #[cfg(feature = "std")]
compile_time_assert_std_error_error<T: std::error::Error>()148 pub fn compile_time_assert_std_error_error<T: std::error::Error>() {}
149
150 /// A test case. A test case consists of a set of named attributes. Every
151 /// attribute in the test case must be consumed exactly once; this helps catch
152 /// typos and omissions.
153 ///
154 /// Requires the `alloc` default feature to be enabled.
155 #[cfg(feature = "alloc")]
156 #[derive(Debug)]
157 pub struct TestCase {
158 attributes: Vec<(String, String, bool)>,
159 }
160
161 #[cfg(feature = "alloc")]
162 impl TestCase {
163 /// Maps the string "true" to true and the string "false" to false.
consume_bool(&mut self, key: &str) -> bool164 pub fn consume_bool(&mut self, key: &str) -> bool {
165 match self.consume_string(key).as_ref() {
166 "true" => true,
167 "false" => false,
168 s => panic!("Invalid bool value: {}", s),
169 }
170 }
171
172 /// Maps the strings "SHA1", "SHA256", "SHA384", and "SHA512" to digest
173 /// algorithms, maps "SHA224" to `None`, and panics on other (erroneous)
174 /// inputs. "SHA224" is mapped to None because *ring* intentionally does
175 /// not support SHA224, but we need to consume test vectors from NIST that
176 /// have SHA224 vectors in them.
consume_digest_alg(&mut self, key: &str) -> Option<&'static digest::Algorithm>177 pub fn consume_digest_alg(&mut self, key: &str) -> Option<&'static digest::Algorithm> {
178 let name = self.consume_string(key);
179 match name.as_ref() {
180 "SHA1" => Some(&digest::SHA1_FOR_LEGACY_USE_ONLY),
181 "SHA224" => None, // We actively skip SHA-224 support.
182 "SHA256" => Some(&digest::SHA256),
183 "SHA384" => Some(&digest::SHA384),
184 "SHA512" => Some(&digest::SHA512),
185 "SHA512_256" => Some(&digest::SHA512_256),
186 _ => panic!("Unsupported digest algorithm: {}", name),
187 }
188 }
189
190 /// Returns the value of an attribute that is encoded as a sequence of an
191 /// even number of hex digits, or as a double-quoted UTF-8 string. The
192 /// empty (zero-length) value is represented as "".
consume_bytes(&mut self, key: &str) -> Vec<u8>193 pub fn consume_bytes(&mut self, key: &str) -> Vec<u8> {
194 let s = self.consume_string(key);
195 if s.starts_with('\"') {
196 // The value is a quoted UTF-8 string.
197
198 let mut bytes = Vec::with_capacity(s.as_bytes().len() - 2);
199 let mut s = s.as_bytes().iter().skip(1);
200 loop {
201 let b = match s.next() {
202 Some(b'\\') => {
203 match s.next() {
204 // We don't allow all octal escape sequences, only "\0" for null.
205 Some(b'0') => 0u8,
206 Some(b't') => b'\t',
207 Some(b'n') => b'\n',
208 // "\xHH"
209 Some(b'x') => {
210 let hi = s.next().expect("Invalid hex escape sequence in string.");
211 let lo = s.next().expect("Invalid hex escape sequence in string.");
212 if let (Ok(hi), Ok(lo)) = (from_hex_digit(*hi), from_hex_digit(*lo))
213 {
214 (hi << 4) | lo
215 } else {
216 panic!("Invalid hex escape sequence in string.");
217 }
218 }
219 _ => {
220 panic!("Invalid hex escape sequence in string.");
221 }
222 }
223 }
224 Some(b'"') => {
225 if s.next().is_some() {
226 panic!("characters after the closing quote of a quoted string.");
227 }
228 break;
229 }
230 Some(b) => *b,
231 None => panic!("Missing terminating '\"' in string literal."),
232 };
233 bytes.push(b);
234 }
235 bytes
236 } else {
237 // The value is hex encoded.
238 match from_hex(&s) {
239 Ok(s) => s,
240 Err(err_str) => {
241 panic!("{} in {}", err_str, s);
242 }
243 }
244 }
245 }
246
247 /// Returns the value of an attribute that is an integer, in decimal
248 /// notation.
consume_usize(&mut self, key: &str) -> usize249 pub fn consume_usize(&mut self, key: &str) -> usize {
250 let s = self.consume_string(key);
251 s.parse::<usize>().unwrap()
252 }
253
254 /// Returns the value of an attribute that is an integer, in decimal
255 /// notation, as a bit length.
256 #[cfg(feature = "alloc")]
consume_usize_bits(&mut self, key: &str) -> bits::BitLength257 pub fn consume_usize_bits(&mut self, key: &str) -> bits::BitLength {
258 let s = self.consume_string(key);
259 let bits = s.parse::<usize>().unwrap();
260 bits::BitLength::from_usize_bits(bits)
261 }
262
263 /// Returns the raw value of an attribute, without any unquoting or
264 /// other interpretation.
consume_string(&mut self, key: &str) -> String265 pub fn consume_string(&mut self, key: &str) -> String {
266 self.consume_optional_string(key)
267 .unwrap_or_else(|| panic!("No attribute named \"{}\"", key))
268 }
269
270 /// Like `consume_string()` except it returns `None` if the test case
271 /// doesn't have the attribute.
consume_optional_string(&mut self, key: &str) -> Option<String>272 pub fn consume_optional_string(&mut self, key: &str) -> Option<String> {
273 for (name, value, consumed) in &mut self.attributes {
274 if key == name {
275 if *consumed {
276 panic!("Attribute {} was already consumed", key);
277 }
278 *consumed = true;
279 return Some(value.clone());
280 }
281 }
282 None
283 }
284 }
285
286 /// References a test input file.
287 #[cfg(feature = "alloc")]
288 #[macro_export]
289 macro_rules! test_file {
290 ($file_name:expr) => {
291 crate::test::File {
292 file_name: $file_name,
293 contents: include_str!($file_name),
294 }
295 };
296 }
297
298 /// A test input file.
299 #[cfg(feature = "alloc")]
300 pub struct File<'a> {
301 /// The name (path) of the file.
302 pub file_name: &'a str,
303
304 /// The contents of the file.
305 pub contents: &'a str,
306 }
307
308 /// Parses test cases out of the given file, calling `f` on each vector until
309 /// `f` fails or until all the test vectors have been read. `f` can indicate
310 /// failure either by returning `Err()` or by panicking.
311 ///
312 /// Requires the `alloc` default feature to be enabled
313 #[cfg(feature = "alloc")]
run<F>(test_file: File, mut f: F) where F: FnMut(&str, &mut TestCase) -> Result<(), error::Unspecified>,314 pub fn run<F>(test_file: File, mut f: F)
315 where
316 F: FnMut(&str, &mut TestCase) -> Result<(), error::Unspecified>,
317 {
318 let lines = &mut test_file.contents.lines();
319
320 let mut current_section = String::from("");
321 let mut failed = false;
322
323 while let Some(mut test_case) = parse_test_case(&mut current_section, lines) {
324 let result = match f(¤t_section, &mut test_case) {
325 Ok(()) => {
326 if !test_case
327 .attributes
328 .iter()
329 .any(|&(_, _, consumed)| !consumed)
330 {
331 Ok(())
332 } else {
333 failed = true;
334 Err("Test didn't consume all attributes.")
335 }
336 }
337 Err(error::Unspecified) => Err("Test returned Err(error::Unspecified)."),
338 };
339
340 if result.is_err() {
341 failed = true;
342 }
343
344 #[cfg(feature = "test_logging")]
345 {
346 if let Err(msg) = result {
347 std::println!("{}: {}", test_file.file_name, msg);
348
349 for (name, value, consumed) in test_case.attributes {
350 let consumed_str = if consumed { "" } else { " (unconsumed)" };
351 std::println!("{}{} = {}", name, consumed_str, value);
352 }
353 };
354 }
355 }
356
357 if failed {
358 panic!("Test failed.")
359 }
360 }
361
362 /// Decode an string of hex digits into a sequence of bytes. The input must
363 /// have an even number of digits.
364 #[cfg(feature = "alloc")]
from_hex(hex_str: &str) -> Result<Vec<u8>, String>365 pub fn from_hex(hex_str: &str) -> Result<Vec<u8>, String> {
366 if hex_str.len() % 2 != 0 {
367 return Err(String::from(
368 "Hex string does not have an even number of digits",
369 ));
370 }
371
372 let mut result = Vec::with_capacity(hex_str.len() / 2);
373 for digits in hex_str.as_bytes().chunks(2) {
374 let hi = from_hex_digit(digits[0])?;
375 let lo = from_hex_digit(digits[1])?;
376 result.push((hi * 0x10) | lo);
377 }
378 Ok(result)
379 }
380
381 #[cfg(feature = "alloc")]
from_hex_digit(d: u8) -> Result<u8, String>382 fn from_hex_digit(d: u8) -> Result<u8, String> {
383 if d >= b'0' && d <= b'9' {
384 Ok(d - b'0')
385 } else if d >= b'a' && d <= b'f' {
386 Ok(d - b'a' + 10u8)
387 } else if d >= b'A' && d <= b'F' {
388 Ok(d - b'A' + 10u8)
389 } else {
390 Err(format!("Invalid hex digit '{}'", d as char))
391 }
392 }
393
394 #[cfg(feature = "alloc")]
parse_test_case( current_section: &mut String, lines: &mut dyn Iterator<Item = &str>, ) -> Option<TestCase>395 fn parse_test_case(
396 current_section: &mut String,
397 lines: &mut dyn Iterator<Item = &str>,
398 ) -> Option<TestCase> {
399 let mut attributes = Vec::new();
400
401 let mut is_first_line = true;
402 loop {
403 let line = lines.next();
404
405 #[cfg(feature = "test_logging")]
406 {
407 if let Some(text) = &line {
408 std::println!("Line: {}", text);
409 }
410 }
411
412 match line {
413 // If we get to EOF when we're not in the middle of a test case,
414 // then we're done.
415 None if is_first_line => {
416 return None;
417 }
418
419 // End of the file on a non-empty test cases ends the test case.
420 None => {
421 return Some(TestCase { attributes });
422 }
423
424 // A blank line ends a test case if the test case isn't empty.
425 Some(ref line) if line.is_empty() => {
426 if !is_first_line {
427 return Some(TestCase { attributes });
428 }
429 // Ignore leading blank lines.
430 }
431
432 // Comments start with '#'; ignore them.
433 Some(ref line) if line.starts_with('#') => (),
434
435 Some(ref line) if line.starts_with('[') => {
436 assert!(is_first_line);
437 assert!(line.ends_with(']'));
438 current_section.truncate(0);
439 current_section.push_str(line);
440 let _ = current_section.pop();
441 let _ = current_section.remove(0);
442 }
443
444 Some(ref line) => {
445 is_first_line = false;
446
447 let parts: Vec<&str> = line.splitn(2, " = ").collect();
448 if parts.len() != 2 {
449 panic!("Syntax error: Expected Key = Value.");
450 };
451
452 let key = parts[0].trim();
453 let value = parts[1].trim();
454
455 // Don't allow the value to be ommitted. An empty value can be
456 // represented as an empty quoted string.
457 assert_ne!(value.len(), 0);
458
459 // Checking is_none() ensures we don't accept duplicate keys.
460 attributes.push((String::from(key), String::from(value), false));
461 }
462 }
463 }
464 }
465
466 /// Deterministic implementations of `ring::rand::SecureRandom`.
467 ///
468 /// These implementations are particularly useful for testing implementations
469 /// of randomized algorithms & protocols using known-answer-tests where the
470 /// test vectors contain the random seed to use. They are also especially
471 /// useful for some types of fuzzing.
472 #[doc(hidden)]
473 pub mod rand {
474 use crate::{error, polyfill, rand};
475
476 /// An implementation of `SecureRandom` that always fills the output slice
477 /// with the given byte.
478 #[derive(Debug)]
479 pub struct FixedByteRandom {
480 pub byte: u8,
481 }
482
483 impl rand::sealed::SecureRandom for FixedByteRandom {
fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified>484 fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
485 polyfill::slice::fill(dest, self.byte);
486 Ok(())
487 }
488 }
489
490 /// An implementation of `SecureRandom` that always fills the output slice
491 /// with the slice in `bytes`. The length of the slice given to `slice`
492 /// must match exactly.
493 #[derive(Debug)]
494 pub struct FixedSliceRandom<'a> {
495 pub bytes: &'a [u8],
496 }
497
498 impl rand::sealed::SecureRandom for FixedSliceRandom<'_> {
fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified>499 fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
500 dest.copy_from_slice(self.bytes);
501 Ok(())
502 }
503 }
504
505 /// An implementation of `SecureRandom` where each slice in `bytes` is a
506 /// test vector for one call to `fill()`. *Not thread-safe.*
507 ///
508 /// The first slice in `bytes` is the output for the first call to
509 /// `fill()`, the second slice is the output for the second call to
510 /// `fill()`, etc. The output slice passed to `fill()` must have exactly
511 /// the length of the corresponding entry in `bytes`. `current` must be
512 /// initialized to zero. `fill()` must be called exactly once for each
513 /// entry in `bytes`.
514 #[derive(Debug)]
515 pub struct FixedSliceSequenceRandom<'a> {
516 /// The value.
517 pub bytes: &'a [&'a [u8]],
518 pub current: core::cell::UnsafeCell<usize>,
519 }
520
521 impl rand::sealed::SecureRandom for FixedSliceSequenceRandom<'_> {
fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified>522 fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
523 let current = unsafe { *self.current.get() };
524 let bytes = self.bytes[current];
525 dest.copy_from_slice(bytes);
526 // Remember that we returned this slice and prepare to return
527 // the next one, if any.
528 unsafe { *self.current.get() += 1 };
529 Ok(())
530 }
531 }
532
533 impl Drop for FixedSliceSequenceRandom<'_> {
drop(&mut self)534 fn drop(&mut self) {
535 // Ensure that `fill()` was called exactly the right number of
536 // times.
537 assert_eq!(unsafe { *self.current.get() }, self.bytes.len());
538 }
539 }
540 }
541
542 #[cfg(test)]
543 mod tests {
544 use crate::{error, test};
545
546 #[test]
one_ok()547 fn one_ok() {
548 test::run(test_file!("test_1_tests.txt"), |_, test_case| {
549 let _ = test_case.consume_string("Key");
550 Ok(())
551 });
552 }
553
554 #[test]
555 #[should_panic(expected = "Test failed.")]
one_err()556 fn one_err() {
557 test::run(test_file!("test_1_tests.txt"), |_, test_case| {
558 let _ = test_case.consume_string("Key");
559 Err(error::Unspecified)
560 });
561 }
562
563 #[test]
564 #[should_panic(expected = "Oh noes!")]
one_panics()565 fn one_panics() {
566 test::run(test_file!("test_1_tests.txt"), |_, test_case| {
567 let _ = test_case.consume_string("Key");
568 panic!("Oh noes!");
569 });
570 }
571
572 #[test]
573 #[should_panic(expected = "Test failed.")]
first_err()574 fn first_err() {
575 err_one(0)
576 }
577
578 #[test]
579 #[should_panic(expected = "Test failed.")]
middle_err()580 fn middle_err() {
581 err_one(1)
582 }
583
584 #[test]
585 #[should_panic(expected = "Test failed.")]
last_err()586 fn last_err() {
587 err_one(2)
588 }
589
err_one(test_to_fail: usize)590 fn err_one(test_to_fail: usize) {
591 let mut n = 0;
592 test::run(test_file!("test_3_tests.txt"), |_, test_case| {
593 let _ = test_case.consume_string("Key");
594 let result = if n != test_to_fail {
595 Ok(())
596 } else {
597 Err(error::Unspecified)
598 };
599 n += 1;
600 result
601 });
602 }
603
604 #[test]
605 #[should_panic(expected = "Oh Noes!")]
first_panic()606 fn first_panic() {
607 panic_one(0)
608 }
609
610 #[test]
611 #[should_panic(expected = "Oh Noes!")]
middle_panic()612 fn middle_panic() {
613 panic_one(1)
614 }
615
616 #[test]
617 #[should_panic(expected = "Oh Noes!")]
last_panic()618 fn last_panic() {
619 panic_one(2)
620 }
621
panic_one(test_to_fail: usize)622 fn panic_one(test_to_fail: usize) {
623 let mut n = 0;
624 test::run(test_file!("test_3_tests.txt"), |_, test_case| {
625 let _ = test_case.consume_string("Key");
626 if n == test_to_fail {
627 panic!("Oh Noes!");
628 };
629 n += 1;
630 Ok(())
631 });
632 }
633
634 #[test]
635 #[should_panic(expected = "Syntax error: Expected Key = Value.")]
syntax_error()636 fn syntax_error() {
637 test::run(test_file!("test_1_syntax_error_tests.txt"), |_, _| Ok(()));
638 }
639 }
640