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