#![warn(missing_docs)]
#![warn(rust_2018_idioms)]
use parking_lot::Mutex;
use std::collections::HashMap;
use once_cell::sync::Lazy;
use rand::rngs::SmallRng;
use rand::Rng;
use std::ops::Deref;
use std::panic::Location;
#[derive(Debug)]
pub struct Buggifier {
buggified_lines: Mutex<HashMap<String, bool>>,
random: Mutex<Option<SmallRng>>,
}
impl Buggifier {
pub fn new(r: SmallRng) -> Self {
Buggifier {
buggified_lines: Mutex::new(HashMap::new()),
random: Mutex::new(Some(r)),
}
}
#[track_caller]
pub fn buggify(&self) -> bool {
let location = Location::caller();
self.handle_buggify(format!("{}:{}", location.file(), location.line()), 0.05)
}
pub fn buggify_with_prob(&self, probability: f64) -> bool {
let location = Location::caller();
self.handle_buggify(
format!("{}:{}", location.file(), location.line()),
probability,
)
}
fn handle_buggify(&self, line: String, probability: f64) -> bool {
let mut lock = self.random.lock();
match (*lock).as_mut() {
None => false,
Some(deterministic_random) => {
let mut already_buggified = self.buggified_lines.lock();
if !already_buggified.contains_key(&line)
&& deterministic_random.gen_bool(probability)
{
already_buggified.insert(line, true);
return true;
}
false
}
}
}
pub fn is_buggify_enabled(&self) -> bool {
self.random.lock().is_some()
}
pub fn enable_buggify(&self, r: SmallRng) {
let mut data = self.random.lock();
*data = Some(r);
}
pub fn disable_buggify(&self) {
let mut data = self.random.lock();
*data = None;
let mut map = self.buggified_lines.lock();
map.clear();
}
}
impl Default for Buggifier {
fn default() -> Self {
Buggifier {
buggified_lines: Mutex::new(HashMap::new()),
random: Mutex::new(None),
}
}
}
#[doc(hidden)]
static BUGGIFIER_INSTANCE: Lazy<Buggifier> = Lazy::new(Buggifier::default);
pub fn buggifier() -> &'static Buggifier {
BUGGIFIER_INSTANCE.deref()
}
#[track_caller]
pub fn buggify() -> bool {
let location = Location::caller();
buggifier().handle_buggify(format!("{}:{}", location.file(), location.line()), 0.05)
}
#[track_caller]
pub fn buggify_with_prob(probability: f64) -> bool {
let location = Location::caller();
buggifier().handle_buggify(
format!("{}:{}", location.file(), location.line()),
probability,
)
}
pub fn is_buggify_enabled() -> bool {
buggifier().is_buggify_enabled()
}
pub fn enable_buggify(r: SmallRng) {
buggifier().enable_buggify(r)
}
pub fn disable_buggify() {
buggifier().disable_buggify()
}
#[cfg(test)]
mod tests {
use crate::{
buggifier, buggify, buggify_with_prob, disable_buggify, enable_buggify, is_buggify_enabled,
Buggifier,
};
use rand::rngs::SmallRng;
use rand::SeedableRng;
use tracing::Level;
#[test]
fn test_buggifier() {
let _ = tracing_subscriber::fmt()
.with_max_level(Level::TRACE)
.with_test_writer()
.try_init();
let b = Buggifier::default();
assert!(!b.is_buggify_enabled());
assert!(!b.buggify_with_prob(1.0), "should not buggified");
{
let data = b.random.lock();
assert!((*data).is_none());
let map = b.buggified_lines.lock();
assert!((*map).is_empty());
}
let random = SmallRng::seed_from_u64(42);
b.enable_buggify(random);
assert!(b.is_buggify_enabled(), "should be activated");
for i in 0..100 {
let result = i == 8;
assert_eq!(
b.buggify(),
result,
"iteration {} should have been {}",
i,
result
);
}
{
let data = b.random.lock();
assert!((*data).is_some());
let map = b.buggified_lines.lock();
assert_eq!((*map).len(), 1);
for key in (*map).keys() {
assert!(key.starts_with(&file!().to_string()));
}
for value in (*map).values() {
assert!(value);
}
}
b.disable_buggify();
assert!(!b.buggify_with_prob(1.0), "should not buggified");
}
#[test]
fn test_static_buggify() {
let _ = tracing_subscriber::fmt()
.with_max_level(Level::TRACE)
.with_test_writer()
.try_init();
disable_buggify();
assert!(!is_buggify_enabled());
assert!(!buggify_with_prob(1.0), "should not buggified");
enable_buggify(SmallRng::seed_from_u64(42));
assert!(is_buggify_enabled(), "should be activated");
for i in 0..100 {
let result = i == 8;
assert_eq!(
buggify(),
result,
"iteration {} should have been {}",
i,
result
);
}
{
let data = buggifier().random.lock();
assert!((*data).is_some());
let map = buggifier().buggified_lines.lock();
assert_eq!((*map).len(), 1);
for key in (*map).keys() {
assert!(key.starts_with(&file!().to_string()));
}
for value in (*map).values() {
assert!(value);
}
}
buggifier().disable_buggify();
assert!(!buggifier().buggify_with_prob(1.0), "should not buggified");
}
}