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(&current_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}