Skip to content

Commit

Permalink
include more examples
Browse files Browse the repository at this point in the history
  • Loading branch information
camshaft committed May 30, 2024
1 parent ad6ec5d commit b4b84bc
Show file tree
Hide file tree
Showing 10 changed files with 378 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ crash-*
*.o
corpus
crashes
perf.data*
*.svg
19 changes: 19 additions & 0 deletions examples/boolean-tree/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "boolean-tree"
version = "0.1.0"
edition = "2021"

[dependencies]
bolero-generator = { path = "../../lib/bolero-generator" }

[dev-dependencies]
bolero = { path = "../../lib/bolero" }

[workspace]
members = ["."]

[profile.fuzz]
inherits = "dev"
opt-level = 3
incremental = false
codegen-units = 1
3 changes: 3 additions & 0 deletions examples/boolean-tree/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# boolean-tree

A simple boolean logic language
38 changes: 38 additions & 0 deletions examples/boolean-tree/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use bolero_generator::TypeGenerator;

thread_local! {
static SHOULD_PANIC: bool = {
#[cfg(bolero_should_panic)]
return true;

#[cfg(not(bolero_should_panic))]
return std::env::var("SHOULD_PANIC").is_ok();
};
}

#[cfg(test)]
mod tests;

#[derive(Clone, Debug, TypeGenerator)]
pub enum Expr {
Value(bool),
And(Box<Expr>, Box<Expr>),
Or(Box<Expr>, Box<Expr>),
Xor(Box<Expr>, Box<Expr>),
Nand(Box<Expr>, Box<Expr>),
Not(Box<Expr>),
}

impl Expr {
pub fn eval(&self) -> bool {
match self {
Expr::Value(value) => *value,
Expr::And(a, b) => a.eval() && b.eval(),
Expr::Or(a, b) => a.eval() || b.eval(),
Expr::Xor(a, b) => a.eval() ^ b.eval(),
Expr::Nand(a, b) => !(a.eval() && b.eval()),
Expr::Not(_) if SHOULD_PANIC.with(|v| *v) => unreachable!(),
Expr::Not(a) => !a.eval(),
}
}
}
12 changes: 12 additions & 0 deletions examples/boolean-tree/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use super::*;
use bolero::check;

#[test]
fn model_test() {
check!()
.with_type::<Expr>()
.with_max_depth(3)
.for_each(|ops| {
let _value = ops.eval();
})
}
16 changes: 16 additions & 0 deletions examples/rle-stack/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "rle-stack"
version = "0.1.0"
edition = "2021"

[dev-dependencies]
bolero = { path = "../../lib/bolero" }

[workspace]
members = ["."]

[profile.fuzz]
inherits = "dev"
opt-level = 3
incremental = false
codegen-units = 1
2 changes: 2 additions & 0 deletions examples/rle-stack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# rle-stack
A stack which compresses value with run-length encoding
154 changes: 154 additions & 0 deletions examples/rle-stack/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use core::fmt;

thread_local! {
static SHOULD_PANIC: bool = {
#[cfg(bolero_should_panic)]
return true;

#[cfg(not(bolero_should_panic))]
return std::env::var("SHOULD_PANIC").is_ok();
};
}

type Counter = u16;

//= https://en.wikipedia.org/wiki/Run-length_encoding
//# Run-length encoding (RLE) is a form of lossless data compression in which
//# runs of data (sequences in which the same data value occurs in many consecutive
//# data elements) are stored as a single data value and count, rather than as
//# the original run.

pub struct RleStack<T> {
stack: Vec<Entry<T>>,
len: usize,
}

impl<T> Default for RleStack<T> {
fn default() -> Self {
Self {
stack: vec![],
len: 0,
}
}
}

impl<T: fmt::Debug> fmt::Debug for RleStack<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RleStack")
.field("stack", &self.stack)
.field("len", &self.len)
.finish()
}
}

impl<T: Clone + Eq> RleStack<T> {
pub fn push(&mut self, value: T) {
self.len += 1;

if let Some(prev) = self.stack.last_mut() {
if prev.merge(&value) {
return;
}
}

self.stack.push(Entry::new(value));
}

pub fn pop(&mut self) -> Option<T> {
let entry = self.stack.last_mut()?;

self.len -= 1;

if entry.additional == 0 {
return self.stack.pop().map(|entry| entry.value);
}
entry.additional -= 1;
Some(entry.value.clone())
}

pub fn clear(&mut self) {
// inject faults into the model if configured
if SHOULD_PANIC.with(|v| *v) {
return;
}

self.len = 0;
self.stack.clear();
}

pub fn len(&self) -> usize {
self.len
}

pub fn is_empty(&self) -> bool {
self.len == 0
}

pub fn iter(&self) -> Iter<T> {
Iter {
entry: 0,
count: 0,
stack: self,
}
}
}

pub struct Iter<'a, T> {
entry: usize,
count: Counter,
stack: &'a RleStack<T>,
}

impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;

fn next(&mut self) -> Option<Self::Item> {
let entry = self.stack.stack.get(self.entry)?;

if entry.additional <= self.count {
self.entry += 1;
self.count = 0;
} else {
self.count += 1;
}

Some(&entry.value)
}
}

struct Entry<T> {
value: T,
additional: Counter,
}

impl<T: fmt::Debug> fmt::Debug for Entry<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Entry")
.field("value", &self.value)
.field("additional", &self.additional)
.finish()
}
}

impl<T> Entry<T> {
pub fn new(value: T) -> Self {
Self {
value,
additional: 0,
}
}
}

impl<T: Eq> Entry<T> {
pub fn merge(&mut self, other: &T) -> bool {
if &self.value == other {
self.additional += 1;
true
} else {
false
}
}
}

#[cfg(test)]
mod tests;
109 changes: 109 additions & 0 deletions examples/rle-stack/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use super::*;
use bolero::{check, generator::*};
use core::fmt;

#[derive(Clone, Copy, Debug, TypeGenerator)]
enum Operation<T> {
Push { count: u8, value: T },
Pop { count: u8 },
Clear,
}

#[derive(Default)]
struct Model<T: Copy + fmt::Debug + Eq> {
oracle: Vec<T>,
subject: RleStack<T>,
}

impl<T: Copy + fmt::Debug + Eq> Model<T> {
pub fn push(&mut self, value: T) {
self.oracle.push(value);
self.subject.push(value);
self.invariants();
}

pub fn pop(&mut self) -> Option<T> {
let expected = self.oracle.pop();
let actual = self.subject.pop();
assert_eq!(expected, actual);
self.invariants();
actual
}

pub fn clear(&mut self) {
self.oracle.clear();
self.subject.clear();
self.invariants();
}

pub fn apply(&mut self, operation: Operation<T>) {
match operation {
Operation::Push { count, value } => {
for _ in 0..count {
self.push(value);
}
}
Operation::Pop { count } => {
for _ in 0..count {
self.pop();
}
}
Operation::Clear => {
self.clear();
}
}
}

pub fn finish(&self) {
self.invariants();
let actual: Vec<_> = self.subject.iter().copied().collect();
assert_eq!(
self.oracle, actual,
"\n\nSubject state: {:#?}",
self.subject
);
}

fn invariants(&self) {
assert_eq!(
self.oracle.len(),
self.subject.len(),
"lengths do not match"
);
assert_eq!(
self.oracle.is_empty(),
self.subject.is_empty(),
"is_empty does not match"
);
}
}

impl<T: Copy + fmt::Debug + Eq> Drop for Model<T> {
fn drop(&mut self) {
if !std::thread::panicking() {
self.finish();
}
}
}

#[test]
fn model_test() {
check!().with_type::<Vec<Operation<u8>>>().for_each(|ops| {
let mut model = Model::default();
for op in ops {
model.apply(*op);
}
})
}

#[test]
fn unit_test() {
let mut model = <Model<u8>>::default();

assert!(model.pop().is_none());
model.clear();
assert!(model.pop().is_none());

model.push(123);
assert_eq!(model.pop(), Some(123));
}
Loading

0 comments on commit b4b84bc

Please sign in to comment.