| // Copyright 2015-2016 Brian Smith. |
| // |
| // Permission to use, copy, modify, and/or distribute this software for any |
| // purpose with or without fee is hereby granted, provided that the above |
| // copyright notice and this permission notice appear in all copies. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES |
| // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY |
| // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| |
| //! Testing framework. |
| //! |
| //! Unlike the rest of *ring*, this testing framework uses panics pretty |
| //! liberally. It was originally designed for internal use--it drives most of |
| //! *ring*'s internal tests, and so it is optimized for getting *ring*'s tests |
| //! written quickly at the expense of some usability. The documentation is |
| //! lacking. The best way to learn it is to look at some examples. The digest |
| //! tests are the most complicated because they use named sections. Other tests |
| //! avoid named sections and so are easier to understand. |
| //! |
| //! # Examples |
| //! |
| //! ## Writing Tests |
| //! |
| //! Input files look like this: |
| //! |
| //! ```text |
| //! # This is a comment. |
| //! |
| //! HMAC = SHA1 |
| //! Input = "My test data" |
| //! Key = "" |
| //! Output = 61afdecb95429ef494d61fdee15990cabf0826fc |
| //! |
| //! HMAC = SHA256 |
| //! Input = "Sample message for keylen<blocklen" |
| //! Key = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F |
| //! Output = A28CF43130EE696A98F14A37678B56BCFCBDD9E5CF69717FECF5480F0EBDF790 |
| //! ``` |
| //! |
| //! Test cases are separated with blank lines. Note how the bytes of the `Key` |
| //! attribute are specified as a quoted string in the first test case and as |
| //! hex in the second test case; you can use whichever form is more convenient |
| //! and you can mix and match within the same file. The empty sequence of bytes |
| //! can only be represented with the quoted string form (`""`). |
| //! |
| //! Here's how you would consume the test data: |
| //! |
| //! ```ignore |
| //! use ring::test; |
| //! |
| //! test::from_file("src/hmac_tests.txt", |section, test_case| { |
| //! assert_eq!(section, ""); // This test doesn't use named sections. |
| //! |
| //! let digest_alg = test_case.consume_digest_alg("HMAC"); |
| //! let input = test_case.consume_bytes("Input"); |
| //! let key = test_case.consume_bytes("Key"); |
| //! let output = test_case.consume_bytes("Output"); |
| //! |
| //! // Do the actual testing here |
| //! }); |
| //! ``` |
| //! |
| //! Note that `consume_digest_alg` automatically maps the string "SHA1" to a |
| //! reference to `digest::SHA1`, "SHA256" to `digest::SHA256`, etc. |
| //! |
| //! ## Output When a Test Fails |
| //! |
| //! When a test case fails, the framework automatically prints out the test |
| //! case. If the test case failed with a panic, then the backtrace of the panic |
| //! will be printed too. For example, let's say the failing test case looks |
| //! like this: |
| //! |
| //! ```text |
| //! Curve = P-256 |
| //! a = 2b11cb945c8cf152ffa4c9c2b1c965b019b35d0b7626919ef0ae6cb9d232f8af |
| //! b = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c |
| //! r = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c |
| //! ``` |
| //! If the test fails, this will be printed (if `$RUST_BACKTRACE` is `1`): |
| //! |
| //! ```text |
| //! src/example_tests.txt: Test panicked. |
| //! Curve = P-256 |
| //! a = 2b11cb945c8cf152ffa4c9c2b1c965b019b35d0b7626919ef0ae6cb9d232f8af |
| //! b = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c |
| //! r = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c |
| //! thread 'example_test' panicked at 'Test failed.', src\test.rs:206 |
| //! stack backtrace: |
| //! 0: 0x7ff654a05c7c - std::rt::lang_start::h61f4934e780b4dfc |
| //! 1: 0x7ff654a04f32 - std::rt::lang_start::h61f4934e780b4dfc |
| //! 2: 0x7ff6549f505d - std::panicking::rust_panic_with_hook::hfe203e3083c2b544 |
| //! 3: 0x7ff654a0825b - rust_begin_unwind |
| //! 4: 0x7ff6549f63af - std::panicking::begin_panic_fmt::h484cd47786497f03 |
| //! 5: 0x7ff654a07e9b - rust_begin_unwind |
| //! 6: 0x7ff654a0ae95 - core::panicking::panic_fmt::h257ceb0aa351d801 |
| //! 7: 0x7ff654a0b190 - core::panicking::panic::h4bb1497076d04ab9 |
| //! 8: 0x7ff65496dc41 - from_file<closure> |
| //! at C:\Users\Example\example\<core macros>:4 |
| //! 9: 0x7ff65496d49c - example_test |
| //! at C:\Users\Example\example\src\example.rs:652 |
| //! 10: 0x7ff6549d192a - test::stats::Summary::new::ha139494ed2e4e01f |
| //! 11: 0x7ff6549d51a2 - test::stats::Summary::new::ha139494ed2e4e01f |
| //! 12: 0x7ff654a0a911 - _rust_maybe_catch_panic |
| //! 13: 0x7ff6549d56dd - test::stats::Summary::new::ha139494ed2e4e01f |
| //! 14: 0x7ff654a03783 - std::sys::thread::Thread::new::h2b08da6cd2517f79 |
| //! 15: 0x7ff968518101 - BaseThreadInitThunk |
| //! ``` |
| //! |
| //! Notice that the output shows the name of the data file |
| //! (`src/example_tests.txt`), the test inputs that led to the failure, and the |
| //! stack trace to the line in the test code that panicked: entry 9 in the |
| //! stack trace pointing to line 652 of the file `example.rs`. |
| |
| #[cfg(feature = "use_heap")] |
| use bits; |
| |
| use {digest, error}; |
| |
| use core; |
| use std; |
| use std::string::String; |
| use std::vec::Vec; |
| //use std::io::BufRead; |
| |
| |
| /// `compile_time_assert_clone::<T>();` fails to compile if `T` doesn't |
| /// implement `Clone`. |
| pub fn compile_time_assert_clone<T: Clone>() {} |
| |
| /// `compile_time_assert_copy::<T>();` fails to compile if `T` doesn't |
| /// implement `Copy`. |
| pub fn compile_time_assert_copy<T: Copy>() {} |
| |
| /// `compile_time_assert_send::<T>();` fails to compile if `T` doesn't |
| /// implement `Send`. |
| pub fn compile_time_assert_send<T: Send>() {} |
| |
| /// `compile_time_assert_sync::<T>();` fails to compile if `T` doesn't |
| /// implement `Sync`. |
| pub fn compile_time_assert_sync<T: Sync>() {} |
| |
| /// `compile_time_assert_debug::<T>();` fails to compile if `T` doesn't |
| /// implement `Debug`. |
| pub fn compile_time_assert_debug<T: core::fmt::Debug>() {} |
| |
| /// A test case. A test case consists of a set of named attributes. Every |
| /// attribute in the test case must be consumed exactly once; this helps catch |
| /// typos and omissions. |
| #[derive(Debug)] |
| pub struct TestCase { |
| attributes: Vec<(String, String, bool)>, |
| } |
| |
| impl TestCase { |
| /// Maps the string "true" to true and the string "false" to false. |
| pub fn consume_bool(&mut self, key: &str) -> bool { |
| match self.consume_string(key).as_ref() { |
| "true" => true, |
| "false" => false, |
| s => panic!("Invalid bool value: {}", s), |
| } |
| } |
| |
| /// Maps the strings "SHA1", "SHA256", "SHA384", and "SHA512" to digest |
| /// algorithms, maps "SHA224" to `None`, and panics on other (erroneous) |
| /// inputs. "SHA224" is mapped to None because *ring* intentionally does |
| /// not support SHA224, but we need to consume test vectors from NIST that |
| /// have SHA224 vectors in them. |
| pub fn consume_digest_alg(&mut self, key: &str) |
| -> Option<&'static digest::Algorithm> { |
| let name = self.consume_string(key); |
| match name.as_ref() { |
| "SHA1" => Some(&digest::SHA1), |
| "SHA224" => None, // We actively skip SHA-224 support. |
| "SHA256" => Some(&digest::SHA256), |
| "SHA384" => Some(&digest::SHA384), |
| "SHA512" => Some(&digest::SHA512), |
| "SHA512_256" => Some(&digest::SHA512_256), |
| _ => panic!("Unsupported digest algorithm: {}", name), |
| } |
| } |
| |
| /// Returns the value of an attribute that is encoded as a sequence of an |
| /// even number of hex digits, or as a double-quoted UTF-8 string. The |
| /// empty (zero-length) value is represented as "". |
| pub fn consume_bytes(&mut self, key: &str) -> Vec<u8> { |
| let s = self.consume_string(key); |
| if s.starts_with('\"') { |
| // The value is a quoted UTF-8 string. |
| |
| let mut bytes = Vec::with_capacity(s.as_bytes().len() - 2); |
| let mut s = s.as_bytes().iter().skip(1); |
| loop { |
| let b = match s.next() { |
| Some(b'\\') => { |
| match s.next() { |
| // We don't allow all octal escape sequences, only "\0" for null. |
| Some(b'0') => 0u8, |
| Some(b't') => b'\t', |
| Some(b'n') => b'\n', |
| // "\xHH" |
| Some(b'x') => { |
| let hi = s.next().expect("Invalid hex escape sequence in string."); |
| let lo = s.next().expect("Invalid hex escape sequence in string."); |
| if let (Ok(hi), Ok(lo)) = |
| (from_hex_digit(*hi), from_hex_digit(*lo)) { |
| (hi << 4) | lo |
| } else { |
| panic!("Invalid hex escape sequence in string."); |
| } |
| }, |
| _ => { |
| panic!("Invalid hex escape sequence in string."); |
| } |
| } |
| }, |
| Some(b'"') => { |
| if s.next().is_some() { |
| panic!("characters after the closing quote of a quoted string."); |
| } |
| break; |
| }, |
| Some(b) => *b, |
| None => { |
| panic!("Missing terminating '\"' in string literal.") |
| } |
| }; |
| bytes.push(b); |
| } |
| bytes |
| } else { |
| // The value is hex encoded. |
| match from_hex(&s) { |
| Ok(s) => s, |
| Err(err_str) => { |
| panic!("{} in {}", err_str, s); |
| }, |
| } |
| } |
| } |
| |
| /// Returns the value of an attribute that is an integer, in decimal |
| /// notation. |
| pub fn consume_usize(&mut self, key: &str) -> usize { |
| let s = self.consume_string(key); |
| s.parse::<usize>().unwrap() |
| } |
| |
| /// Returns the value of an attribute that is an integer, in decimal |
| /// notation, as a bit length. |
| #[cfg(feature = "use_heap")] |
| pub fn consume_usize_bits(&mut self, key: &str) -> bits::BitLength { |
| let s = self.consume_string(key); |
| let bits = s.parse::<usize>().unwrap(); |
| bits::BitLength::from_usize_bits(bits) |
| } |
| |
| /// Returns the raw value of an attribute, without any unquoting or |
| /// other interpretation. |
| pub fn consume_string(&mut self, key: &str) -> String { |
| self.consume_optional_string(key) |
| .unwrap_or_else(|| panic!("No attribute named \"{}\"", key)) |
| } |
| |
| /// Like `consume_string()` except it returns `None` if the test case |
| /// doesn't have the attribute. |
| pub fn consume_optional_string(&mut self, key: &str) -> Option<String> { |
| for (name, value, consumed) in |
| &mut self.attributes { |
| if key == name { |
| if *consumed { |
| panic!("Attribute {} was already consumed", key); |
| } |
| *consumed = true; |
| return Some(value.clone()); |
| } |
| } |
| None |
| } |
| } |
| |
| /// Returns the path for *ring* source code root. |
| /// |
| /// On iOS, source are assumed to be copied in the application bundle, as |
| /// a "src" directory along the test runner. |
| #[cfg(target_os = "ios")] |
| pub fn ring_src_path() -> std::path::PathBuf { |
| std::env::current_exe().unwrap().parent().unwrap().join("src") |
| } |
| |
| /// Returns the path for *ring* source code root. |
| /// |
| /// On most platforms, the tests are run by cargo, so it's just the current |
| /// working directory. |
| #[cfg(not(target_os = "ios"))] |
| pub fn ring_src_path() -> std::path::PathBuf { |
| std::path::PathBuf::from(".") |
| } |
| |
| /// Reads test cases out of the file with the path given by |
| /// `test_data_relative_file_path`, calling `f` on each vector until `f` fails |
| /// or until all the test vectors have been read. `f` can indicate failure |
| /// either by returning `Err()` or by panicking. |
| pub fn from_file<F>(_test_data_relative_file_path: &str, mut _f: F) |
| where F: FnMut(&str, &mut TestCase) |
| -> Result<(), error::Unspecified> { |
| unimplemented!(); |
| // let path = ring_src_path().join(test_data_relative_file_path); |
| // let file = std::fs::File::open(path).unwrap(); |
| // let mut lines = std::io::BufReader::new(&file).lines(); |
| // |
| // let mut current_section = String::from(""); |
| // let mut failed = false; |
| // |
| // #[allow(box_pointers)] |
| // while let Some(mut test_case) = parse_test_case(&mut current_section, |
| // &mut lines) { |
| // let result = |
| // std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { |
| // f(¤t_section, &mut test_case) |
| // })); |
| // let result = match result { |
| // Ok(Ok(())) => { |
| // if !test_case.attributes.iter().any( |
| // |&(_, _, consumed)| !consumed) { |
| // Ok(()) |
| // } else { |
| // failed = true; |
| // Err("Test didn't consume all attributes.") |
| // } |
| // }, |
| // Ok(Err(_)) => Err("Test returned Err(error::Unspecified)."), |
| // Err(_) => Err("Test panicked."), |
| // }; |
| // |
| // if let Err(msg) = result { |
| // failed = true; |
| // |
| // println!("{}: {}", test_data_relative_file_path, msg); |
| // for (name, value, consumed) in test_case.attributes { |
| // let consumed_str = if consumed { "" } else { " (unconsumed)" }; |
| // println!("{}{} = {}", name, consumed_str, value); |
| // } |
| // }; |
| // } |
| // |
| // if failed { |
| // panic!("Test failed.") |
| // } |
| } |
| |
| /// Decode an string of hex digits into a sequence of bytes. The input must |
| /// have an even number of digits. |
| pub fn from_hex(hex_str: &str) -> Result<Vec<u8>, String> { |
| if hex_str.len() % 2 != 0 { |
| return Err( |
| String::from("Hex string does not have an even number of digits")); |
| } |
| |
| let mut result = Vec::with_capacity(hex_str.len() / 2); |
| for digits in hex_str.as_bytes().chunks(2) { |
| let hi = from_hex_digit(digits[0])?; |
| let lo = from_hex_digit(digits[1])?; |
| result.push((hi * 0x10) | lo); |
| } |
| Ok(result) |
| } |
| |
| fn from_hex_digit(d: u8) -> Result<u8, String> { |
| if d >= b'0' && d <= b'9' { |
| Ok(d - b'0') |
| } else if d >= b'a' && d <= b'f' { |
| Ok(d - b'a' + 10u8) |
| } else if d >= b'A' && d <= b'F' { |
| Ok(d - b'A' + 10u8) |
| } else { |
| Err(format!("Invalid hex digit '{}'", d as char)) |
| } |
| } |
| |
| //type FileLines<'a> = std::io::Lines<std::io::BufReader<&'a std::fs::File>>; |
| // |
| //fn parse_test_case(current_section: &mut String, lines: &mut FileLines) |
| // -> Option<TestCase> { |
| // let mut attributes = Vec::new(); |
| // |
| // let mut is_first_line = true; |
| // loop { |
| // let line = match lines.next() { |
| // None => None, |
| // Some(result) => Some(result.unwrap()), |
| // }; |
| // |
| // if cfg!(feature = "test_logging") { |
| // if let Some(text) = &line { |
| // println!("Line: {}", text); |
| // } |
| // } |
| // |
| // match line { |
| // // If we get to EOF when we're not in the middle of a test case, |
| // // then we're done. |
| // None if is_first_line => { |
| // return None; |
| // }, |
| // |
| // // End of the file on a non-empty test cases ends the test case. |
| // None => { |
| // return Some(TestCase { attributes }); |
| // }, |
| // |
| // // A blank line ends a test case if the test case isn't empty. |
| // Some(ref line) if line.is_empty() => { |
| // if !is_first_line { |
| // return Some(TestCase { attributes }); |
| // } |
| // // Ignore leading blank lines. |
| // }, |
| // |
| // // Comments start with '#'; ignore them. |
| // Some(ref line) if line.starts_with('#') => {}, |
| // |
| // Some(ref line) if line.starts_with('[') => { |
| // assert!(is_first_line); |
| // assert!(line.ends_with(']')); |
| // current_section.truncate(0); |
| // current_section.push_str(line); |
| // let _ = current_section.pop(); |
| // let _ = current_section.remove(0); |
| // }, |
| // |
| // Some(ref line) => { |
| // is_first_line = false; |
| // |
| // let parts: Vec<&str> = line.splitn(2, " = ").collect(); |
| // if parts.len() != 2 { |
| // panic!("Syntax error: Expected Key = Value."); |
| // }; |
| // |
| // let key = parts[0].trim(); |
| // let value = parts[1].trim(); |
| // |
| // // Don't allow the value to be ommitted. An empty value can be |
| // // represented as an empty quoted string. |
| // assert_ne!(value.len(), 0); |
| // |
| // // Checking is_none() ensures we don't accept duplicate keys. |
| // attributes.push((String::from(key), String::from(value), false)); |
| // }, |
| // } |
| // } |
| //} |
| |
| /// Deterministic implementations of `ring::rand::SecureRandom`. |
| /// |
| /// These implementations are particularly useful for testing implementations |
| /// of randomized algorithms & protocols using known-answer-tests where the |
| /// test vectors contain the random seed to use. They are also especially |
| /// useful for some types of fuzzing. |
| #[allow(missing_docs)] |
| pub mod rand { |
| use core; |
| use {error, polyfill, rand}; |
| use private; |
| |
| /// An implementation of `SecureRandom` that always fills the output slice |
| /// with the given byte. |
| #[derive(Debug)] |
| pub struct FixedByteRandom { |
| pub byte: u8, |
| } |
| |
| impl rand::SecureRandom for FixedByteRandom { |
| fn fill(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> { |
| polyfill::slice::fill(dest, self.byte); |
| Ok(()) |
| } |
| } |
| |
| /// An implementation of `SecureRandom` that always fills the output slice |
| /// with the slice in `bytes`. The length of the slice given to `slice` |
| /// must match exactly. |
| #[derive(Debug)] |
| pub struct FixedSliceRandom<'a> { |
| pub bytes: &'a [u8], |
| } |
| |
| impl<'a> rand::SecureRandom for FixedSliceRandom<'a> { |
| fn fill(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> { |
| dest.copy_from_slice(self.bytes); |
| Ok(()) |
| } |
| } |
| |
| /// An implementation of `SecureRandom` where each slice in `bytes` is a |
| /// test vector for one call to `fill()`. *Not thread-safe.* |
| /// |
| /// The first slice in `bytes` is the output for the first call to |
| /// `fill()`, the second slice is the output for the second call to |
| /// `fill()`, etc. The output slice passed to `fill()` must have exactly |
| /// the length of the corresponding entry in `bytes`. `current` must be |
| /// initialized to zero. `fill()` must be called exactly once for each |
| /// entry in `bytes`. |
| #[derive(Debug)] |
| pub struct FixedSliceSequenceRandom<'a> { |
| /// The value. |
| pub bytes: &'a [&'a [u8]], |
| pub current: core::cell::UnsafeCell<usize>, |
| } |
| |
| impl<'a> rand::SecureRandom for FixedSliceSequenceRandom<'a> { |
| fn fill(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> { |
| let current = unsafe { *self.current.get() }; |
| let bytes = self.bytes[current]; |
| dest.copy_from_slice(bytes); |
| // Remember that we returned this slice and prepare to return |
| // the next one, if any. |
| unsafe { *self.current.get() += 1 }; |
| Ok(()) |
| } |
| } |
| |
| impl<'a> Drop for FixedSliceSequenceRandom<'a> { |
| fn drop(&mut self) { |
| // Ensure that `fill()` was called exactly the right number of |
| // times. |
| assert_eq!(unsafe { *self.current.get() }, self.bytes.len()); |
| } |
| } |
| |
| impl private::Sealed for FixedByteRandom {} |
| impl<'a> private::Sealed for FixedSliceRandom<'a> {} |
| impl<'a> private::Sealed for FixedSliceSequenceRandom<'a> {} |
| |
| } |
| |
| |
| #[cfg(test)] |
| mod tests { |
| use {error, test}; |
| |
| #[test] |
| fn one_ok() { |
| test::from_file("src/test_1_tests.txt", |_, test_case| { |
| let _ = test_case.consume_string("Key"); |
| Ok(()) |
| }); |
| } |
| |
| #[test] |
| #[should_panic(expected = "Test failed.")] |
| fn one_err() { |
| test::from_file("src/test_1_tests.txt", |_, test_case| { |
| let _ = test_case.consume_string("Key"); |
| Err(error::Unspecified) |
| }); |
| } |
| |
| #[test] |
| #[should_panic(expected = "Test failed.")] |
| fn one_panics() { |
| test::from_file("src/test_1_tests.txt", |_, test_case| { |
| let _ = test_case.consume_string("Key"); |
| panic!(""); |
| }); |
| } |
| |
| #[test] |
| #[should_panic(expected = "Test failed.")] |
| fn first_err() { err_one(0) } |
| |
| #[test] |
| #[should_panic(expected = "Test failed.")] |
| fn middle_err() { err_one(1) } |
| |
| #[test] |
| #[should_panic(expected = "Test failed.")] |
| fn last_err() { err_one(2) } |
| |
| fn err_one(test_to_fail: usize) { |
| let mut n = 0; |
| test::from_file("src/test_3_tests.txt", |_, test_case| { |
| let _ = test_case.consume_string("Key"); |
| let result = if n != test_to_fail { |
| Ok(()) |
| } else { |
| Err(error::Unspecified) |
| }; |
| n += 1; |
| result |
| }); |
| } |
| |
| #[test] |
| #[should_panic(expected = "Test failed.")] |
| fn first_panic() { panic_one(0) } |
| |
| #[test] |
| #[should_panic(expected = "Test failed.")] |
| fn middle_panic() { panic_one(1) } |
| |
| #[test] |
| #[should_panic(expected = "Test failed.")] |
| fn last_panic() { panic_one(2) } |
| |
| fn panic_one(test_to_fail: usize) { |
| let mut n = 0; |
| test::from_file("src/test_3_tests.txt", |_, test_case| { |
| let _ = test_case.consume_string("Key"); |
| if n == test_to_fail { |
| panic!("Oh Noes!"); |
| }; |
| n += 1; |
| Ok(()) |
| }); |
| } |
| |
| #[test] |
| #[should_panic(expected = "Syntax error: Expected Key = Value.")] |
| fn syntax_error() { |
| test::from_file("src/test_1_syntax_error_tests.txt", |_, _| Ok(())); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn file_not_found() { |
| test::from_file("src/test_file_not_found_tests.txt", |_, _| Ok(())); |
| } |
| } |