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 | extern crate alloc; |
121 | |
122 | use alloc::{format, string::String, vec::Vec}; |
123 | |
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`. |
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`. |
135 | pub fn compile_time_assert_copy<T: Copy>() {} |
136 | |
137 | /// `compile_time_assert_eq::<T>();` fails to compile if `T` doesn't |
138 | /// implement `Eq`. |
139 | pub fn compile_time_assert_eq<T: Eq>() {} |
140 | |
141 | /// `compile_time_assert_send::<T>();` fails to compile if `T` doesn't |
142 | /// implement `Send`. |
143 | pub fn compile_time_assert_send<T: Send>() {} |
144 | |
145 | /// `compile_time_assert_sync::<T>();` fails to compile if `T` doesn't |
146 | /// implement `Sync`. |
147 | pub fn compile_time_assert_sync<T: Sync>() {} |
148 | |
149 | /// `compile_time_assert_std_error_error::<T>();` fails to compile if `T` |
150 | /// doesn't implement `std::error::Error`. |
151 | #[cfg (feature = "std" )] |
152 | pub fn compile_time_assert_std_error_error<T: std::error::Error>() {} |
153 | |
154 | /// A test case. A test case consists of a set of named attributes. Every |
155 | /// attribute in the test case must be consumed exactly once; this helps catch |
156 | /// typos and omissions. |
157 | /// |
158 | /// Requires the `alloc` default feature to be enabled. |
159 | #[derive (Debug)] |
160 | pub struct TestCase { |
161 | attributes: Vec<(String, String, bool)>, |
162 | } |
163 | |
164 | impl TestCase { |
165 | /// Maps the string "true" to true and the string "false" to false. |
166 | pub fn consume_bool(&mut self, key: &str) -> bool { |
167 | match self.consume_string(key).as_ref() { |
168 | "true" => true, |
169 | "false" => false, |
170 | s => panic!("Invalid bool value: {}" , s), |
171 | } |
172 | } |
173 | |
174 | /// Maps the strings "SHA1", "SHA256", "SHA384", and "SHA512" to digest |
175 | /// algorithms, maps "SHA224" to `None`, and panics on other (erroneous) |
176 | /// inputs. "SHA224" is mapped to None because *ring* intentionally does |
177 | /// not support SHA224, but we need to consume test vectors from NIST that |
178 | /// have SHA224 vectors in them. |
179 | pub fn consume_digest_alg(&mut self, key: &str) -> Option<&'static digest::Algorithm> { |
180 | let name = self.consume_string(key); |
181 | match name.as_ref() { |
182 | "SHA1" => Some(&digest::SHA1_FOR_LEGACY_USE_ONLY), |
183 | "SHA224" => None, // We actively skip SHA-224 support. |
184 | "SHA256" => Some(&digest::SHA256), |
185 | "SHA384" => Some(&digest::SHA384), |
186 | "SHA512" => Some(&digest::SHA512), |
187 | "SHA512_256" => Some(&digest::SHA512_256), |
188 | _ => panic!("Unsupported digest algorithm: {}" , name), |
189 | } |
190 | } |
191 | |
192 | /// Returns the value of an attribute that is encoded as a sequence of an |
193 | /// even number of hex digits, or as a double-quoted UTF-8 string. The |
194 | /// empty (zero-length) value is represented as "". |
195 | pub fn consume_bytes(&mut self, key: &str) -> Vec<u8> { |
196 | self.consume_optional_bytes(key) |
197 | .unwrap_or_else(|| panic!("No attribute named \"{}\"" , key)) |
198 | } |
199 | |
200 | /// Like `consume_bytes()` except it returns `None` if the test case |
201 | /// doesn't have the attribute. |
202 | pub fn consume_optional_bytes(&mut self, key: &str) -> Option<Vec<u8>> { |
203 | let s = self.consume_optional_string(key)?; |
204 | let result = if s.starts_with(' \"' ) { |
205 | // The value is a quoted UTF-8 string. |
206 | |
207 | let mut bytes = Vec::with_capacity(s.as_bytes().len() - 2); |
208 | let mut s = s.as_bytes().iter().skip(1); |
209 | loop { |
210 | let b = match s.next() { |
211 | Some(b' \\' ) => { |
212 | match s.next() { |
213 | // We don't allow all octal escape sequences, only "\0" for null. |
214 | Some(b'0' ) => 0u8, |
215 | Some(b't' ) => b' \t' , |
216 | Some(b'n' ) => b' \n' , |
217 | // "\xHH" |
218 | Some(b'x' ) => { |
219 | let hi = s.next().expect("Invalid hex escape sequence in string." ); |
220 | let lo = s.next().expect("Invalid hex escape sequence in string." ); |
221 | if let (Ok(hi), Ok(lo)) = (from_hex_digit(*hi), from_hex_digit(*lo)) |
222 | { |
223 | (hi << 4) | lo |
224 | } else { |
225 | panic!("Invalid hex escape sequence in string." ); |
226 | } |
227 | } |
228 | _ => { |
229 | panic!("Invalid hex escape sequence in string." ); |
230 | } |
231 | } |
232 | } |
233 | Some(b'"' ) => { |
234 | if s.next().is_some() { |
235 | panic!("characters after the closing quote of a quoted string." ); |
236 | } |
237 | break; |
238 | } |
239 | Some(b) => *b, |
240 | None => panic!("Missing terminating ' \"' in string literal." ), |
241 | }; |
242 | bytes.push(b); |
243 | } |
244 | bytes |
245 | } else { |
246 | // The value is hex encoded. |
247 | match from_hex(&s) { |
248 | Ok(s) => s, |
249 | Err(err_str) => { |
250 | panic!(" {} in {}" , err_str, s); |
251 | } |
252 | } |
253 | }; |
254 | Some(result) |
255 | } |
256 | |
257 | /// Returns the value of an attribute that is an integer, in decimal |
258 | /// notation. |
259 | pub fn consume_usize(&mut self, key: &str) -> usize { |
260 | let s = self.consume_string(key); |
261 | s.parse::<usize>().unwrap() |
262 | } |
263 | |
264 | /// Returns the value of an attribute that is an integer, in decimal |
265 | /// notation, as a bit length. |
266 | pub fn consume_usize_bits(&mut self, key: &str) -> bits::BitLength { |
267 | let s = self.consume_string(key); |
268 | let bits = s.parse::<usize>().unwrap(); |
269 | bits::BitLength::from_usize_bits(bits) |
270 | } |
271 | |
272 | /// Returns the raw value of an attribute, without any unquoting or |
273 | /// other interpretation. |
274 | pub fn consume_string(&mut self, key: &str) -> String { |
275 | self.consume_optional_string(key) |
276 | .unwrap_or_else(|| panic!("No attribute named \"{}\"" , key)) |
277 | } |
278 | |
279 | /// Like `consume_string()` except it returns `None` if the test case |
280 | /// doesn't have the attribute. |
281 | pub fn consume_optional_string(&mut self, key: &str) -> Option<String> { |
282 | for (name, value, consumed) in &mut self.attributes { |
283 | if key == name { |
284 | if *consumed { |
285 | panic!("Attribute {} was already consumed" , key); |
286 | } |
287 | *consumed = true; |
288 | return Some(value.clone()); |
289 | } |
290 | } |
291 | None |
292 | } |
293 | } |
294 | |
295 | /// References a test input file. |
296 | #[macro_export ] |
297 | macro_rules! test_file { |
298 | ($file_name:expr) => { |
299 | $crate::test::File { |
300 | file_name: $file_name, |
301 | contents: include_str!($file_name), |
302 | } |
303 | }; |
304 | } |
305 | |
306 | /// A test input file. |
307 | pub struct File<'a> { |
308 | /// The name (path) of the file. |
309 | pub file_name: &'a str, |
310 | |
311 | /// The contents of the file. |
312 | pub contents: &'a str, |
313 | } |
314 | |
315 | /// Parses test cases out of the given file, calling `f` on each vector until |
316 | /// `f` fails or until all the test vectors have been read. `f` can indicate |
317 | /// failure either by returning `Err()` or by panicking. |
318 | pub fn run<F>(test_file: File, mut f: F) |
319 | where |
320 | F: FnMut(&str, &mut TestCase) -> Result<(), error::Unspecified>, |
321 | { |
322 | let lines = &mut test_file.contents.lines(); |
323 | |
324 | let mut current_section = String::from("" ); |
325 | let mut failed = false; |
326 | |
327 | while let Some(mut test_case) = parse_test_case(&mut current_section, lines) { |
328 | let result = match f(¤t_section, &mut test_case) { |
329 | Ok(()) => { |
330 | if !test_case |
331 | .attributes |
332 | .iter() |
333 | .any(|&(_, _, consumed)| !consumed) |
334 | { |
335 | Ok(()) |
336 | } else { |
337 | failed = true; |
338 | Err("Test didn't consume all attributes." ) |
339 | } |
340 | } |
341 | Err(error::Unspecified) => Err("Test returned Err(error::Unspecified)." ), |
342 | }; |
343 | |
344 | if result.is_err() { |
345 | failed = true; |
346 | } |
347 | |
348 | #[cfg (feature = "test_logging" )] |
349 | if let Err(msg) = result { |
350 | std::println!(" {}: {}" , test_file.file_name, msg); |
351 | |
352 | for (name, value, consumed) in test_case.attributes { |
353 | let consumed_str = if consumed { "" } else { " (unconsumed)" }; |
354 | std::println!(" {}{} = {}" , name, consumed_str, value); |
355 | } |
356 | }; |
357 | } |
358 | |
359 | if failed { |
360 | panic!("Test failed." ) |
361 | } |
362 | } |
363 | |
364 | /// Decode an string of hex digits into a sequence of bytes. The input must |
365 | /// have an even number of digits. |
366 | pub fn from_hex(hex_str: &str) -> Result<Vec<u8>, String> { |
367 | if hex_str.len() % 2 != 0 { |
368 | return Err(String::from( |
369 | "Hex string does not have an even number of digits" , |
370 | )); |
371 | } |
372 | |
373 | let mut result: Vec = Vec::with_capacity(hex_str.len() / 2); |
374 | for digits: &[u8] in hex_str.as_bytes().chunks(chunk_size:2) { |
375 | let hi: u8 = from_hex_digit(digits[0])?; |
376 | let lo: u8 = from_hex_digit(digits[1])?; |
377 | result.push((hi * 0x10) | lo); |
378 | } |
379 | Ok(result) |
380 | } |
381 | |
382 | fn from_hex_digit(d: u8) -> Result<u8, String> { |
383 | use core::ops::RangeInclusive; |
384 | const DECIMAL: (u8, RangeInclusive<u8>) = (0, b'0' ..=b'9' ); |
385 | const HEX_LOWER: (u8, RangeInclusive<u8>) = (10, b'a' ..=b'f' ); |
386 | const HEX_UPPER: (u8, RangeInclusive<u8>) = (10, b'A' ..=b'F' ); |
387 | for (offset: &u8, range: &RangeInclusive) in &[DECIMAL, HEX_LOWER, HEX_UPPER] { |
388 | if range.contains(&d) { |
389 | return Ok(d - range.start() + offset); |
390 | } |
391 | } |
392 | Err(format!("Invalid hex digit ' {}'" , d as char)) |
393 | } |
394 | |
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 | if let Some(text) = &line { |
407 | std::println!("Line: {}" , text); |
408 | } |
409 | |
410 | match line { |
411 | // If we get to EOF when we're not in the middle of a test case, |
412 | // then we're done. |
413 | None if is_first_line => { |
414 | return None; |
415 | } |
416 | |
417 | // End of the file on a non-empty test cases ends the test case. |
418 | None => { |
419 | return Some(TestCase { attributes }); |
420 | } |
421 | |
422 | // A blank line ends a test case if the test case isn't empty. |
423 | Some("" ) => { |
424 | if !is_first_line { |
425 | return Some(TestCase { attributes }); |
426 | } |
427 | // Ignore leading blank lines. |
428 | } |
429 | |
430 | // Comments start with '#'; ignore them. |
431 | Some(line) if line.starts_with('#' ) => (), |
432 | |
433 | Some(line) if line.starts_with('[' ) => { |
434 | assert!(is_first_line); |
435 | assert!(line.ends_with(']' )); |
436 | current_section.truncate(0); |
437 | current_section.push_str(line); |
438 | let _ = current_section.pop(); |
439 | let _ = current_section.remove(0); |
440 | } |
441 | |
442 | Some(line) => { |
443 | is_first_line = false; |
444 | |
445 | let parts: Vec<&str> = line.splitn(2, " = " ).collect(); |
446 | if parts.len() != 2 { |
447 | panic!("Syntax error: Expected Key = Value." ); |
448 | }; |
449 | |
450 | let key = parts[0].trim(); |
451 | let value = parts[1].trim(); |
452 | |
453 | // Don't allow the value to be ommitted. An empty value can be |
454 | // represented as an empty quoted string. |
455 | assert_ne!(value.len(), 0); |
456 | |
457 | // Checking is_none() ensures we don't accept duplicate keys. |
458 | attributes.push((String::from(key), String::from(value), false)); |
459 | } |
460 | } |
461 | } |
462 | } |
463 | |
464 | /// Deterministic implementations of `ring::rand::SecureRandom`. |
465 | /// |
466 | /// These implementations are particularly useful for testing implementations |
467 | /// of randomized algorithms & protocols using known-answer-tests where the |
468 | /// test vectors contain the random seed to use. They are also especially |
469 | /// useful for some types of fuzzing. |
470 | #[doc (hidden)] |
471 | pub mod rand { |
472 | use crate::{error, rand}; |
473 | |
474 | /// An implementation of `SecureRandom` that always fills the output slice |
475 | /// with the given byte. |
476 | #[derive (Debug)] |
477 | pub struct FixedByteRandom { |
478 | pub byte: u8, |
479 | } |
480 | |
481 | impl rand::sealed::SecureRandom for FixedByteRandom { |
482 | fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> { |
483 | dest.fill(self.byte); |
484 | Ok(()) |
485 | } |
486 | } |
487 | |
488 | /// An implementation of `SecureRandom` that always fills the output slice |
489 | /// with the slice in `bytes`. The length of the slice given to `slice` |
490 | /// must match exactly. |
491 | #[derive (Debug)] |
492 | pub struct FixedSliceRandom<'a> { |
493 | pub bytes: &'a [u8], |
494 | } |
495 | |
496 | impl rand::sealed::SecureRandom for FixedSliceRandom<'_> { |
497 | fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> { |
498 | dest.copy_from_slice(self.bytes); |
499 | Ok(()) |
500 | } |
501 | } |
502 | |
503 | /// An implementation of `SecureRandom` where each slice in `bytes` is a |
504 | /// test vector for one call to `fill()`. *Not thread-safe.* |
505 | /// |
506 | /// The first slice in `bytes` is the output for the first call to |
507 | /// `fill()`, the second slice is the output for the second call to |
508 | /// `fill()`, etc. The output slice passed to `fill()` must have exactly |
509 | /// the length of the corresponding entry in `bytes`. `current` must be |
510 | /// initialized to zero. `fill()` must be called exactly once for each |
511 | /// entry in `bytes`. |
512 | #[derive (Debug)] |
513 | pub struct FixedSliceSequenceRandom<'a> { |
514 | /// The value. |
515 | pub bytes: &'a [&'a [u8]], |
516 | pub current: core::cell::UnsafeCell<usize>, |
517 | } |
518 | |
519 | impl rand::sealed::SecureRandom for FixedSliceSequenceRandom<'_> { |
520 | fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> { |
521 | let current = unsafe { *self.current.get() }; |
522 | let bytes = self.bytes[current]; |
523 | dest.copy_from_slice(bytes); |
524 | // Remember that we returned this slice and prepare to return |
525 | // the next one, if any. |
526 | unsafe { *self.current.get() += 1 }; |
527 | Ok(()) |
528 | } |
529 | } |
530 | |
531 | impl Drop for FixedSliceSequenceRandom<'_> { |
532 | fn drop(&mut self) { |
533 | // Ensure that `fill()` was called exactly the right number of |
534 | // times. |
535 | assert_eq!(unsafe { *self.current.get() }, self.bytes.len()); |
536 | } |
537 | } |
538 | } |
539 | |
540 | #[cfg (test)] |
541 | mod tests { |
542 | use crate::{error, test}; |
543 | |
544 | #[test ] |
545 | fn one_ok() { |
546 | test::run(test_file!("test_1_tests.txt" ), |_, test_case| { |
547 | let _ = test_case .consume_string("Key" ); |
548 | Ok(()) |
549 | }); |
550 | } |
551 | |
552 | #[test ] |
553 | #[should_panic (expected = "Test failed." )] |
554 | fn one_err() { |
555 | test::run(test_file!("test_1_tests.txt" ), |_, test_case| { |
556 | let _ = test_case .consume_string("Key" ); |
557 | Err(error::Unspecified) |
558 | }); |
559 | } |
560 | |
561 | #[test ] |
562 | #[should_panic (expected = "Oh noes!" )] |
563 | fn one_panics() { |
564 | test::run(test_file!("test_1_tests.txt" ), |_, test_case| { |
565 | let _ = test_case .consume_string("Key" ); |
566 | panic!("Oh noes!" ); |
567 | }); |
568 | } |
569 | |
570 | #[test ] |
571 | #[should_panic (expected = "Test failed." )] |
572 | fn first_err() { |
573 | err_one(0) |
574 | } |
575 | |
576 | #[test ] |
577 | #[should_panic (expected = "Test failed." )] |
578 | fn middle_err() { |
579 | err_one(1) |
580 | } |
581 | |
582 | #[test ] |
583 | #[should_panic (expected = "Test failed." )] |
584 | fn last_err() { |
585 | err_one(2) |
586 | } |
587 | |
588 | fn err_one(test_to_fail: usize) { |
589 | let mut n = 0; |
590 | test::run(test_file!("test_3_tests.txt" ), |_, test_case| { |
591 | let _ = test_case .consume_string("Key" ); |
592 | let result = if n != test_to_fail { |
593 | Ok(()) |
594 | } else { |
595 | Err(error::Unspecified) |
596 | }; |
597 | n += 1; |
598 | result |
599 | }); |
600 | } |
601 | |
602 | #[test ] |
603 | #[should_panic (expected = "Oh Noes!" )] |
604 | fn first_panic() { |
605 | panic_one(0) |
606 | } |
607 | |
608 | #[test ] |
609 | #[should_panic (expected = "Oh Noes!" )] |
610 | fn middle_panic() { |
611 | panic_one(1) |
612 | } |
613 | |
614 | #[test ] |
615 | #[should_panic (expected = "Oh Noes!" )] |
616 | fn last_panic() { |
617 | panic_one(2) |
618 | } |
619 | |
620 | fn panic_one(test_to_fail: usize) { |
621 | let mut n = 0; |
622 | test::run(test_file!("test_3_tests.txt" ), |_, test_case| { |
623 | let _ = test_case .consume_string("Key" ); |
624 | if n == test_to_fail { |
625 | panic!("Oh Noes!" ); |
626 | }; |
627 | n += 1; |
628 | Ok(()) |
629 | }); |
630 | } |
631 | |
632 | #[test ] |
633 | #[should_panic (expected = "Syntax error: Expected Key = Value." )] |
634 | fn syntax_error() { |
635 | test::run(test_file!("test_1_syntax_error_tests.txt" ), |_, _| Ok(())); |
636 | } |
637 | } |
638 | |