frizbee 0.10.0

Fast typo-resistant fuzzy matching via SIMD smith waterman, similar algorithm to FZF/FZY
Documentation
//! Deterministic fuzz-input generator shared by the backend parity tests and
//! the public-API property tests (`tests/api_properties.rs`, which pulls this
//! file in via `#[path]`).
//!
//! [`ByteCursor`] turns a raw `&[u8]` into structured values. Deriving a test
//! case from raw bytes (rather than from a native proptest strategy) lets the
//! *same* generator drive both proptest (random bytes) and miri (a tiny fixed
//! corpus) — proptest is too slow under miri, but the fixed corpus exercises
//! the identical code path. Each suite layers its own "case" type on top.
//!
//! Each consumer uses only a subset of the cursor's alphabet (`byte`/`bytes`
//! for the backend's raw-byte haystacks, `char`/`string` for the API's `&str`),
//! hence the module-wide `dead_code` allowance.

#![allow(dead_code)]

/// A deterministic reader that turns a byte buffer into structured test values.
/// When the buffer runs dry it keeps yielding a deterministic pseudo-sequence,
/// so a case can always be fully constructed regardless of input length.
pub struct ByteCursor<'a> {
    input: &'a [u8],
    pos: usize,
}

impl<'a> ByteCursor<'a> {
    pub fn new(input: &'a [u8]) -> Self {
        Self { input, pos: 0 }
    }

    pub fn next(&mut self) -> u8 {
        let byte = if self.input.is_empty() {
            (self.pos as u8).wrapping_mul(29).wrapping_add(7)
        } else {
            self.input[self.pos % self.input.len()]
                .wrapping_add(((self.pos / self.input.len()) as u8).wrapping_mul(19))
        };
        self.pos += 1;
        byte
    }

    pub fn bool(&mut self) -> bool {
        self.next() & 1 == 1
    }

    pub fn usize(&mut self) -> usize {
        let mut value = 0usize;
        for shift in (0..usize::BITS as usize).step_by(8) {
            value |= (self.next() as usize) << shift;
        }
        value
    }

    /// A length in `0..=max`, snapped to one of `boundaries` a quarter of the
    /// time so chunk-edge lengths are over-represented.
    pub fn len(&mut self, max: usize, boundaries: &[usize]) -> usize {
        if self.next().is_multiple_of(4) {
            boundaries[(self.next() as usize) % boundaries.len()].min(max)
        } else {
            self.usize() % (max + 1)
        }
    }

    pub fn bytes(&mut self, len: usize) -> Vec<u8> {
        (0..len).map(|_| self.byte()).collect()
    }

    /// A byte drawn from a fuzz-friendly alphabet: delimiters, both letter
    /// cases, and digits, weighted toward characters that exercise the scoring
    /// bonuses (delimiters, capitalization).
    pub fn byte(&mut self) -> u8 {
        let byte = self.next();
        match byte % 16 {
            0 => b'a',
            1 => b' ',
            2 => b'/',
            3 => b'.',
            4 => b',',
            5 => b'_',
            6 => b'-',
            7 => b':',
            8..=10 => b'a' + (byte % 26),
            11..=13 => b'A' + (byte % 26),
            _ => b'0' + (byte % 10),
        }
    }

    pub fn string(&mut self, len: usize) -> String {
        (0..len).map(|_| self.char()).collect()
    }

    /// Like [`byte`](Self::byte) but yields `char`s, occasionally emitting a
    /// multi-byte scalar (Latin, Arabic, Hangul, emoji) so the unicode paths
    /// are exercised.
    pub fn char(&mut self) -> char {
        let byte = self.next();
        if byte & 0x0f == 0 {
            return match (byte >> 4) & 3 {
                0 => 'é',
                1 => 'ن',
                2 => '',
                _ => '😀',
            };
        }

        match byte % 18 {
            0 => 'a',
            1 => ' ',
            2 => '/',
            3 => '.',
            4 => ',',
            5 => '_',
            6 => '-',
            7 => ':',
            8..=11 => (b'a' + (byte % 26)) as char,
            12..=15 => (b'A' + (byte % 26)) as char,
            _ => (b'0' + (byte % 10)) as char,
        }
    }
}

/// Cap a value under miri, where the interpreter is orders of magnitude slower.
pub fn test_bound(max: usize, miri_max: usize) -> usize {
    if cfg!(miri) { max.min(miri_max) } else { max }
}

/// Scale an iteration count down under miri.
pub fn test_iterations(default: usize) -> usize {
    if cfg!(miri) { default.min(4) } else { default }
}

/// Drive `check_input` over `iterations` proptest-generated byte buffers,
/// turning any panic into a reported proptest failure.
pub fn run_generated_inputs<F>(iterations: usize, max_len: usize, check_input: F)
where
    F: Fn(&[u8]),
{
    use proptest::prelude::*;
    use proptest::test_runner::{Config as ProptestConfig, TestCaseError, TestRunner};
    use std::panic::{AssertUnwindSafe, catch_unwind};

    let strategy = prop::collection::vec(any::<u8>(), 0..=max_len);
    let mut runner = TestRunner::new(ProptestConfig {
        cases: test_iterations(iterations) as u32,
        ..ProptestConfig::default()
    });

    runner
        .run(&strategy, |input| {
            catch_unwind(AssertUnwindSafe(|| check_input(&input)))
                .map_err(|payload| TestCaseError::fail(panic_payload_to_string(payload)))?;
            Ok(())
        })
        .unwrap();
}

fn panic_payload_to_string(payload: Box<dyn std::any::Any + Send>) -> String {
    if let Some(message) = payload.downcast_ref::<String>() {
        message.clone()
    } else if let Some(message) = payload.downcast_ref::<&'static str>() {
        (*message).to_owned()
    } else {
        "property assertion panicked".to_owned()
    }
}