Skip to main content

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            #[cfg(all(feature = "unstable", not(feature = "fips")))]
185            "KECCAK256" => Some(&crate::digest::sha::KECCAK256),
186            _ => unreachable!("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 "".
193    pub fn consume_bytes(&mut self, key: &str) -> Vec<u8> {
194        self.consume_optional_bytes(key)
195            .unwrap_or_else(|| panic!("No attribute named \"{key}\""))
196    }
197
198    /// Like `consume_bytes()` except it returns `None` if the test case
199    /// doesn't have the attribute.
200    pub fn consume_optional_bytes(&mut self, key: &str) -> Option<Vec<u8>> {
201        let s = self.consume_optional_string(key)?;
202        let result = if s.starts_with('\"') {
203            // The value is a quoted UTF-8 string.
204            let s = s.as_bytes();
205            let mut bytes = Vec::with_capacity(s.len());
206            let mut s = s.iter().skip(1);
207            loop {
208                let b = match s.next() {
209                    Some(b'\\') => {
210                        match s.next() {
211                            // We don't allow all octal escape sequences, only "\0" for null.
212                            Some(b'0') => 0u8,
213                            Some(b't') => b'\t',
214                            Some(b'n') => b'\n',
215                            _ => {
216                                panic!("Invalid hex escape sequence in string.");
217                            }
218                        }
219                    }
220                    Some(b'"') => {
221                        assert!(
222                            s.next().is_none(),
223                            "characters after the closing quote of a quoted string."
224                        );
225                        break;
226                    }
227                    Some(b) => *b,
228                    None => panic!("Missing terminating '\"' in string literal."),
229                };
230                bytes.push(b);
231            }
232            bytes
233        } else {
234            // The value is hex encoded.
235            match from_hex(&s) {
236                Ok(s) => s,
237                Err(err_str) => {
238                    panic!("{err_str} in {s}");
239                }
240            }
241        };
242        Some(result)
243    }
244
245    /// Returns the value of an attribute that is an integer, in decimal
246    /// notation.
247    pub fn consume_usize(&mut self, key: &str) -> usize {
248        let s = self.consume_string(key);
249        s.parse::<usize>().unwrap()
250    }
251
252    /// Returns the value of an attribute that is an integer, in decimal
253    /// notation.
254    pub fn consume_bool(&mut self, key: &str) -> bool {
255        let value_str = self
256            .consume_optional_string(key)
257            .unwrap_or_else(|| panic!("No attribute named \"{key}\""))
258            .to_ascii_lowercase();
259        value_str.starts_with('t') || value_str.starts_with('y')
260    }
261
262    /// Returns the raw value of an attribute, without any unquoting or
263    /// other interpretation.
264    pub fn consume_string(&mut self, key: &str) -> String {
265        self.consume_optional_string(key)
266            .unwrap_or_else(|| panic!("No attribute named \"{key}\""))
267    }
268
269    /// Like `consume_string()` except it returns `None` if the test case
270    /// doesn't have the attribute.
271    pub fn consume_optional_string(&mut self, key: &str) -> Option<String> {
272        for (name, value, consumed) in &mut self.attributes {
273            if key == name {
274                assert!(!(*consumed), "Attribute {key} was already consumed");
275                *consumed = true;
276                return Some(value.clone());
277            }
278        }
279        None
280    }
281}
282
283/// References a test input file.
284#[macro_export]
285#[allow(clippy::module_name_repetitions)]
286macro_rules! test_file {
287    ($file_name: expr) => {
288        $crate::test::File {
289            file_name: $file_name,
290            contents: include_str!($file_name),
291        }
292    };
293}
294
295/// A test input file.
296#[derive(Clone, Copy)]
297pub struct File<'a> {
298    /// The name (path) of the file.
299    pub file_name: &'a str,
300
301    /// The contents of the file.
302    pub contents: &'a str,
303}
304
305/// Parses test cases out of the given file, calling `f` on each vector until
306/// `f` fails or until all the test vectors have been read. `f` can indicate
307/// failure either by returning `Err()` or by panicking.
308///
309/// # Panics
310/// Panics on test failure.
311#[allow(clippy::needless_pass_by_value)]
312pub fn run<F>(test_file: File, mut f: F)
313where
314    F: FnMut(&str, &mut TestCase) -> Result<(), error::Unspecified>,
315{
316    let lines = &mut test_file.contents.lines();
317
318    let mut current_section = String::new();
319    let mut failed = false;
320
321    while let Some(mut test_case) = parse_test_case(&mut current_section, lines) {
322        let result = match f(&current_section, &mut test_case) {
323            Ok(()) => {
324                if test_case
325                    .attributes
326                    .iter()
327                    .any(|&(_, _, consumed)| !consumed)
328                {
329                    failed = true;
330                    Err("Test didn't consume all attributes.")
331                } else {
332                    Ok(())
333                }
334            }
335            Err(error::Unspecified) => Err("Test returned Err(error::Unspecified)."),
336        };
337
338        if result.is_err() {
339            failed = true;
340        }
341
342        #[cfg(feature = "test_logging")]
343        {
344            if let Err(msg) = result {
345                println!("{}: {}", test_file.file_name, msg);
346
347                for (name, value, consumed) in test_case.attributes {
348                    let consumed_str = if consumed { "" } else { " (unconsumed)" };
349                    println!("{}{} = {}", name, consumed_str, value);
350                }
351            };
352        }
353    }
354
355    assert!(!failed, "Test failed.");
356}
357
358fn parse_test_case(
359    current_section: &mut String,
360    lines: &mut dyn Iterator<Item = &str>,
361) -> Option<TestCase> {
362    let mut attributes = Vec::new();
363
364    let mut is_first_line = true;
365    loop {
366        let line = lines.next();
367
368        #[cfg(feature = "test_logging")]
369        {
370            if let Some(text) = &line {
371                println!("Line: {}", text);
372            }
373        }
374
375        match line {
376            // If we get to EOF when we're not in the middle of a test case,
377            // then we're done.
378            None if is_first_line => {
379                return None;
380            }
381
382            // End of the file on a non-empty test cases ends the test case.
383            None => {
384                return Some(TestCase { attributes });
385            }
386
387            // A blank line ends a test case if the test case isn't empty.
388            Some("") => {
389                if !is_first_line {
390                    return Some(TestCase { attributes });
391                }
392                // Ignore leading blank lines.
393            }
394
395            // Comments start with '#'; ignore them.
396            Some(line) if line.starts_with('#') => (),
397
398            Some(line) if line.starts_with('[') => {
399                assert!(is_first_line);
400                assert!(line.ends_with(']'));
401                current_section.clear();
402                current_section.push_str(line);
403                let _: Option<char> = current_section.pop();
404                let _: char = current_section.remove(0);
405            }
406
407            Some(line) => {
408                is_first_line = false;
409
410                let parts: Vec<&str> = line.splitn(2, " = ").collect();
411                assert_eq!(parts.len(), 2, "Syntax error: Expected Key = Value.");
412
413                let key = parts[0].trim();
414                let value = parts[1].trim();
415
416                // Don't allow the value to be ommitted. An empty value can be
417                // represented as an empty quoted string.
418                assert_ne!(value.len(), 0);
419
420                // Checking is_none() ensures we don't accept duplicate keys.
421                attributes.push((String::from(key), String::from(value), false));
422            }
423        }
424    }
425}
426
427/// Deterministic implementations of `ring::rand::SecureRandom`.
428///
429/// These are only used for testing KATs where a random number should be generated.
430pub mod rand {
431    use crate::error;
432
433    /// An implementation of `SecureRandom` that always fills the output slice
434    /// with the given byte.
435    #[derive(Debug)]
436    pub struct FixedByteRandom {
437        pub byte: u8,
438    }
439
440    impl crate::rand::sealed::SecureRandom for FixedByteRandom {
441        fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
442            dest.fill(self.byte);
443            Ok(())
444        }
445    }
446
447    /// An implementation of `SecureRandom` that always fills the output slice
448    /// with the slice in `bytes`. The length of the slice given to `slice`
449    /// must match exactly.
450    #[derive(Debug)]
451    pub struct FixedSliceRandom<'a> {
452        pub bytes: &'a [u8],
453    }
454
455    impl crate::rand::sealed::SecureRandom for FixedSliceRandom<'_> {
456        #[inline]
457        fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
458            dest.copy_from_slice(self.bytes);
459            Ok(())
460        }
461    }
462
463    /// An implementation of `SecureRandom` where each slice in `bytes` is a
464    /// test vector for one call to `fill()`. *Not thread-safe.*
465    ///
466    /// The first slice in `bytes` is the output for the first call to
467    /// `fill()`, the second slice is the output for the second call to
468    /// `fill()`, etc. The output slice passed to `fill()` must have exactly
469    /// the length of the corresponding entry in `bytes`. `current` must be
470    /// initialized to zero. `fill()` must be called exactly once for each
471    /// entry in `bytes`.
472    #[derive(Debug)]
473    pub struct FixedSliceSequenceRandom<'a> {
474        /// The value.
475        pub bytes: &'a [&'a [u8]],
476        pub current: core::cell::UnsafeCell<usize>,
477    }
478
479    impl crate::rand::sealed::SecureRandom for FixedSliceSequenceRandom<'_> {
480        fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
481            let current = unsafe { *self.current.get() };
482            let bytes = self.bytes[current];
483            dest.copy_from_slice(bytes);
484            // Remember that we returned this slice and prepare to return
485            // the next one, if any.
486            unsafe { *self.current.get() += 1 };
487            Ok(())
488        }
489    }
490
491    impl Drop for FixedSliceSequenceRandom<'_> {
492        fn drop(&mut self) {
493            // Ensure that `fill()` was called exactly the right number of
494            // times.
495            assert_eq!(unsafe { *self.current.get() }, self.bytes.len());
496        }
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use crate::rand::sealed::SecureRandom;
503    use crate::test::rand::{FixedByteRandom, FixedSliceRandom, FixedSliceSequenceRandom};
504    use crate::test::{from_dirty_hex, to_hex_upper};
505    use crate::{error, test};
506    use core::cell::UnsafeCell;
507
508    #[test]
509    fn fixed_byte_random() {
510        let fbr = FixedByteRandom { byte: 42 };
511        let mut bs = [0u8; 42];
512        fbr.fill_impl(&mut bs).expect("filled");
513        assert_eq!([42u8; 42], bs);
514    }
515
516    #[test]
517    fn fixed_slice_random() {
518        let fbr = FixedSliceRandom { bytes: &[42u8; 42] };
519        let mut bs = [0u8; 42];
520        fbr.fill_impl(&mut bs).expect("fill");
521    }
522
523    #[test]
524    #[should_panic(
525        expected = "source slice length (42) does not match destination slice length (0)"
526    )]
527    fn fixed_slice_random_length_mismatch() {
528        let fbr = FixedSliceRandom { bytes: &[42u8; 42] };
529        let _: Result<(), error::Unspecified> = fbr.fill_impl(&mut []);
530    }
531
532    #[test]
533    fn fixed_slice_sequence_random() {
534        let fbr = FixedSliceSequenceRandom {
535            bytes: &[&[7u8; 7], &[42u8; 42]],
536            current: UnsafeCell::new(0),
537        };
538        let mut bs_one = [0u8; 7];
539        fbr.fill_impl(&mut bs_one).expect("fill");
540        assert_eq!([7u8; 7], bs_one);
541        let mut bs_two = [42u8; 42];
542        fbr.fill_impl(&mut bs_two).expect("filled");
543        assert_eq!([42u8; 42], bs_two);
544    }
545
546    #[test]
547    #[should_panic(expected = "index out of bounds: the len is 0 but the index is 0")]
548    fn fixed_slice_sequence_random_no_remaining() {
549        let fbr = FixedSliceSequenceRandom {
550            bytes: &[],
551            current: UnsafeCell::new(0),
552        };
553        let mut bs_one = [0u8; 7];
554        let _: Result<(), error::Unspecified> = fbr.fill_impl(&mut bs_one);
555    }
556
557    // TODO: This test is causing a thread panic which prevents capture with should_panic
558    // #[test]
559    // #[should_panic]
560    // fn fixed_slice_sequence_random_length_mismatch() {
561    //     let fbr = FixedSliceSequenceRandom {
562    //         bytes: &[&[42u8; 42]],
563    //         current: UnsafeCell::new(0),
564    //     };
565    //     let _: Result<(), error::Unspecified> = fbr.fill_impl(&mut []);
566    // }
567
568    #[test]
569    fn one_ok() {
570        test::run(test_file!("test/test_1_tests.txt"), |_, test_case| {
571            test_case.consume_string("Key");
572            Ok(())
573        });
574    }
575
576    #[test]
577    #[should_panic(expected = "Test failed.")]
578    fn one_err() {
579        test::run(test_file!("test/test_1_tests.txt"), |_, test_case| {
580            test_case.consume_string("Key");
581            Err(error::Unspecified)
582        });
583    }
584
585    #[test]
586    #[should_panic(expected = "Oh noes!")]
587    fn one_panics() {
588        test::run(test_file!("test/test_1_tests.txt"), |_, test_case| {
589            test_case.consume_string("Key");
590            panic!("Oh noes!");
591        });
592    }
593
594    #[test]
595    #[should_panic(expected = "Test failed.")]
596    fn first_err() {
597        err_one(0);
598    }
599
600    #[test]
601    #[should_panic(expected = "Test failed.")]
602    fn middle_err() {
603        err_one(1);
604    }
605
606    #[test]
607    #[should_panic(expected = "Test failed.")]
608    fn last_err() {
609        err_one(2);
610    }
611
612    fn err_one(test_to_fail: usize) {
613        let mut n = 0;
614        test::run(test_file!("test/test_3_tests.txt"), |_, test_case| {
615            test_case.consume_string("Key");
616            let result = if n == test_to_fail {
617                Err(error::Unspecified)
618            } else {
619                Ok(())
620            };
621            n += 1;
622            result
623        });
624    }
625
626    #[test]
627    #[should_panic(expected = "Oh Noes!")]
628    fn first_panic() {
629        panic_one(0);
630    }
631
632    #[test]
633    #[should_panic(expected = "Oh Noes!")]
634    fn middle_panic() {
635        panic_one(1);
636    }
637
638    #[test]
639    #[should_panic(expected = "Oh Noes!")]
640    fn last_panic() {
641        panic_one(2);
642    }
643
644    fn panic_one(test_to_fail: usize) {
645        let mut n = 0;
646        test::run(test_file!("test/test_3_tests.txt"), |_, test_case| {
647            test_case.consume_string("Key");
648            assert_ne!(n, test_to_fail, "Oh Noes!");
649            n += 1;
650            Ok(())
651        });
652    }
653
654    #[test]
655    #[should_panic(expected = "Syntax error: Expected Key = Value.")]
656    fn syntax_error() {
657        test::run(test_file!("test/test_1_syntax_error_tests.txt"), |_, _| {
658            Ok(())
659        });
660    }
661
662    #[test]
663    fn test_to_hex_upper() {
664        let hex = "abcdef0123";
665        let bytes = from_dirty_hex(hex);
666        assert_eq!(hex.to_ascii_uppercase(), to_hex_upper(bytes));
667    }
668}