Skip to main content

test_fuzz_runtime/
lib.rs

1use internal::{
2    dirs::{
3        corpus_directory_from_args_type, generic_args_directory_from_args_type,
4        impl_generic_args_directory_from_args_type,
5    },
6    serde_format,
7};
8use serde::{Serialize, de::DeserializeOwned};
9use sha1::{Digest, Sha1};
10use std::{
11    any::type_name,
12    env,
13    fmt::{self, Debug, Formatter},
14    fs::{create_dir_all, write},
15    io::{self, Read, Write},
16    marker::PhantomData,
17    path::Path,
18    sync::Once,
19};
20
21pub use num_traits;
22
23pub mod traits;
24
25// smoelius: TryDebug, etc. use Nikolai Vazquez's trick from `impls`.
26// https://github.com/nvzqz/impls#how-it-works
27
28struct DebugUnimplemented<T>(PhantomData<T>);
29
30impl<T> Debug for DebugUnimplemented<T> {
31    // smoelius: The third party-tests check for the absence of "unknown of type..." messages. So if
32    // the messages change, the third-party tests must be updated.
33    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
34        const PAT: &str = "TryDebug<";
35        let type_name = type_name::<T>();
36        let pos = type_name.find(PAT).unwrap() + PAT.len();
37        write!(
38            f,
39            "<unknown of type {}>",
40            type_name[pos..].strip_suffix('>').unwrap()
41        )
42    }
43}
44
45pub trait TryDebugFallback {
46    fn apply<U>(&self, f: &mut dyn FnMut(&dyn Debug) -> U) -> U;
47}
48
49impl<T> TryDebugFallback for T {
50    fn apply<U>(&self, f: &mut dyn FnMut(&dyn Debug) -> U) -> U {
51        f(&DebugUnimplemented::<Self>(PhantomData))
52    }
53}
54
55pub struct TryDebug<'a, T>(pub &'a T);
56
57impl<T: Debug> TryDebug<'_, T> {
58    pub fn apply<U>(&self, f: &mut dyn FnMut(&dyn Debug) -> U) -> U {
59        f(self.0)
60    }
61}
62
63/// * `ty` - Type. When `auto_impl!` is used in the `auto!` macro, this should be just `$ty`.
64/// * `trait` - Trait that `ty` may or may not implement.
65/// * `expr` - Vector of values using `trait`, should `ty` implement it. In `expr`, `T` should be
66///   used as the name of the type, not `$ty`.
67#[macro_export]
68macro_rules! auto_impl {
69    ($ty:ty, $trait:path, $expr:expr) => {{
70        trait AutoFallback<U> {
71            fn auto() -> Vec<U>;
72        }
73
74        impl<T, U> AutoFallback<U> for T {
75            #[must_use]
76            fn auto() -> Vec<U> {
77                vec![]
78            }
79        }
80
81        struct Auto<T>(std::marker::PhantomData<T>);
82
83        impl<T: $trait> Auto<T> {
84            #[must_use]
85            pub fn auto() -> Vec<T> {
86                $expr
87            }
88        }
89
90        Auto::<$ty>::auto() as Vec<$ty>
91    }};
92}
93
94#[macro_export]
95macro_rules! auto {
96    ($ty:ty) => {{
97        let xss = [
98            $crate::auto_impl!(
99                $ty,
100                $crate::num_traits::bounds::Bounded,
101                vec![T::min_value(), T::max_value()]
102            ),
103            $crate::auto_impl!($ty, Default, vec![T::default()]),
104            $crate::auto_impl!(
105                $ty,
106                $crate::traits::MaxValueSubOne,
107                vec![T::max_value_sub_one()]
108            ),
109            $crate::auto_impl!($ty, $crate::traits::Middle, vec![T::low(), T::high()]),
110            $crate::auto_impl!(
111                $ty,
112                $crate::traits::MinValueAddOne,
113                vec![T::min_value_add_one()]
114            ),
115        ];
116        IntoIterator::into_iter(xss).flatten()
117    }};
118}
119
120pub fn warn_if_test_fuzz_not_enabled() {
121    static ONCE: Once = Once::new();
122    ONCE.call_once(|| {
123        if !test_fuzz_enabled() {
124            #[allow(clippy::explicit_write)]
125            writeln!(
126                io::stderr(),
127                "If you are trying to run a test-fuzz-generated fuzzing harness, be sure to run \
128                 with `TEST_FUZZ=1`."
129            )
130            .unwrap();
131        }
132    });
133}
134
135#[must_use]
136pub fn test_fuzz_enabled() -> bool {
137    enabled("")
138}
139
140#[must_use]
141pub fn coverage_enabled() -> bool {
142    enabled("COVERAGE")
143}
144
145#[must_use]
146pub fn display_enabled() -> bool {
147    enabled("DISPLAY")
148}
149
150#[must_use]
151pub fn pretty_print_enabled() -> bool {
152    enabled("PRETTY_PRINT")
153}
154
155#[must_use]
156pub fn replay_enabled() -> bool {
157    enabled("REPLAY")
158}
159
160#[must_use]
161pub fn write_enabled() -> bool {
162    enabled("WRITE")
163}
164
165#[must_use]
166fn enabled(opt: &str) -> bool {
167    let key = "TEST_FUZZ".to_owned() + if opt.is_empty() { "" } else { "_" } + opt;
168    env::var(key).is_ok_and(|value| value != "0")
169}
170
171pub fn write_impl_generic_args<T>(args: &[&str]) {
172    let impl_generic_args = impl_generic_args_directory_from_args_type::<T>();
173    let data = args.join(", ");
174    write_data(&impl_generic_args, data.as_bytes()).unwrap();
175}
176
177pub fn write_generic_args<T>(args: &[&str]) {
178    let generic_args = generic_args_directory_from_args_type::<T>();
179    let data = args.join(", ");
180    write_data(&generic_args, data.as_bytes()).unwrap();
181}
182
183pub fn write_args<T: Serialize>(args: &T) {
184    let corpus = corpus_directory_from_args_type::<T>();
185    let data = serde_format::serialize(args);
186    write_data(&corpus, &data).unwrap();
187}
188
189pub fn write_data(dir: &Path, data: &[u8]) -> io::Result<()> {
190    create_dir_all(dir).unwrap_or_default();
191    let hex = {
192        let digest = Sha1::digest(data);
193        hex::encode(digest)
194    };
195    let path_buf = dir.join(hex);
196    write(path_buf, data)
197}
198
199pub fn read_args<T: DeserializeOwned, R: Read>(reader: R) -> Option<T> {
200    serde_format::deserialize(reader)
201}