test-fuzz-runtime 1.0.0

test-fuzz-runtime
Documentation
use internal::{
    dirs::{
        concretizations_directory_from_args_type, corpus_directory_from_args_type,
        impl_concretizations_directory_from_args_type,
    },
    SerdeFormat,
};
use serde::{de::DeserializeOwned, Serialize};
use sha1::{Digest, Sha1};
use std::{
    any::type_name,
    env,
    fmt::{self, Debug, Formatter},
    fs::{create_dir_all, write},
    io::{self, Read},
    marker::PhantomData,
    path::Path,
};

pub use num_traits;

pub mod traits;

#[cfg(feature = "__serde_bincode")]
const BYTE_LIMIT: u64 = 1024 * 1024 * 1024;

// smoelius: TryDebug, etc. use Nikolai Vazquez's trick from `impls`.
// https://github.com/nvzqz/impls#how-it-works

struct DebugUnimplemented<T>(PhantomData<T>);

impl<T> Debug for DebugUnimplemented<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        const PAT: &str = "TryDebug<";
        let type_name = type_name::<T>();
        let pos = type_name.find(PAT).unwrap() + PAT.len();
        write!(
            f,
            "<unknown of type {}>",
            type_name[pos..].strip_suffix('>').unwrap()
        )
    }
}

pub trait TryDebugFallback {
    fn apply<U>(&self, f: &mut dyn FnMut(&dyn Debug) -> U) -> U;
}

impl<T> TryDebugFallback for T {
    fn apply<U>(&self, f: &mut dyn FnMut(&dyn Debug) -> U) -> U {
        f(&DebugUnimplemented::<Self>(PhantomData))
    }
}

pub struct TryDebug<'a, T>(pub &'a T);

impl<'a, T: Debug> TryDebug<'a, T> {
    pub fn apply<U>(&self, f: &mut dyn FnMut(&dyn Debug) -> U) -> U {
        f(self.0)
    }
}

/// * `ty` - Type. When `auto_impl!` is used in the `auto!` macro, this should be just `$ty`.
/// * `trait` - Trait that `ty` may or may not implement.
/// * `expr` - Vector of values using `trait`, should `ty` implement it. In `expr`, `T` should be
///   used as the name of the type, not `$ty`.
#[macro_export]
macro_rules! auto_impl {
    ($ty:ty, $trait:path, $expr:expr) => {{
        trait AutoFallback<U> {
            fn auto() -> Vec<U>;
        }

        impl<T, U> AutoFallback<U> for T {
            #[must_use]
            fn auto() -> Vec<U> {
                vec![]
            }
        }

        struct Auto<T>(std::marker::PhantomData<T>);

        impl<T: $trait> Auto<T> {
            #[must_use]
            pub fn auto() -> Vec<T> {
                $expr
            }
        }

        Auto::<$ty>::auto() as Vec<$ty>
    }};
}

#[macro_export]
macro_rules! auto {
    ($ty:ty) => {{
        let xss = [
            $crate::auto_impl!(
                $ty,
                $crate::num_traits::bounds::Bounded,
                vec![T::min_value(), T::max_value()]
            ),
            $crate::auto_impl!($ty, Default, vec![T::default()]),
            $crate::auto_impl!(
                $ty,
                $crate::traits::MaxValueSubOne,
                vec![T::max_value_sub_one()]
            ),
            $crate::auto_impl!($ty, $crate::traits::Middle, vec![T::low(), T::high()]),
            $crate::auto_impl!(
                $ty,
                $crate::traits::MinValueAddOne,
                vec![T::min_value_add_one()]
            ),
        ];
        std::array::IntoIter::new(xss).flatten()
    }};
}

#[must_use]
pub fn test_fuzz_enabled() -> bool {
    enabled("")
}

#[must_use]
pub fn display_enabled() -> bool {
    enabled("DISPLAY")
}

#[must_use]
pub fn pretty_print_enabled() -> bool {
    enabled("PRETTY_PRINT")
}

#[must_use]
pub fn replay_enabled() -> bool {
    enabled("REPLAY")
}

#[must_use]
pub fn write_enabled() -> bool {
    enabled("WRITE")
}

#[must_use]
fn enabled(opt: &str) -> bool {
    let key = "TEST_FUZZ".to_owned() + if opt.is_empty() { "" } else { "_" } + opt;
    env::var(key).map_or(false, |value| value != "0")
}

pub fn write_impl_concretization<T>(args: &[&str]) {
    let impl_concretizations = impl_concretizations_directory_from_args_type::<T>();
    let data = args.join(", ");
    write_data(&impl_concretizations, data.as_bytes()).unwrap();
}

pub fn write_concretization<T>(args: &[&str]) {
    let concretizations = concretizations_directory_from_args_type::<T>();
    let data = args.join(", ");
    write_data(&concretizations, data.as_bytes()).unwrap();
}

#[allow(unused_variables)]
pub fn write_args<T: Serialize>(serde_format: SerdeFormat, args: &T) {
    let corpus = corpus_directory_from_args_type::<T>();
    match serde_format {
        #[cfg(feature = "__serde_bincode")]
        SerdeFormat::Bincode => {
            use bincode::Options;
            let data = bincode::options()
                .with_limit(BYTE_LIMIT)
                .serialize(args)
                .unwrap();
            write_data(&corpus, &data).unwrap();
        }
        #[cfg(feature = "__serde_cbor")]
        SerdeFormat::Cbor => {
            let data = serde_cbor::to_vec(args).unwrap();
            write_data(&corpus, &data).unwrap();
        }
    };
}

pub fn write_data(dir: &Path, data: &[u8]) -> io::Result<()> {
    create_dir_all(&dir).unwrap_or_default();
    let hex = {
        let hash = Sha1::digest(data);
        hex::encode(hash)
    };
    let path = dir.join(hex);
    write(path, &data)
}

pub fn read_args<T: DeserializeOwned, R: Read>(serde_format: SerdeFormat, reader: R) -> Option<T> {
    match serde_format {
        #[cfg(feature = "__serde_bincode")]
        SerdeFormat::Bincode => {
            use bincode::Options;
            bincode::options()
                .with_limit(BYTE_LIMIT)
                .deserialize_from(reader)
                .ok()
        }
        #[cfg(feature = "__serde_cbor")]
        SerdeFormat::Cbor => serde_cbor::from_reader(reader).ok(),
    }
}