Skip to content

Commit

Permalink
chore: use proptest's value tree implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAFrench committed Jun 15, 2024
1 parent fbb97ae commit 976c62c
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 203 deletions.
118 changes: 8 additions & 110 deletions tooling/fuzzer/src/strategies/int.rs
Original file line number Diff line number Diff line change
@@ -1,94 +1,14 @@
use proptest::{
strategy::{NewTree, Strategy, ValueTree},
strategy::{NewTree, Strategy},
test_runner::TestRunner,
};
use rand::Rng;

/// Value tree for signed ints (up to int256).
pub struct IntValueTree {
/// Lower base (by absolute value)
lo: i128,
/// Current value
curr: i128,
/// Higher base (by absolute value)
hi: i128,
/// If true cannot be simplified or complexified
fixed: bool,
}

impl IntValueTree {
/// Create a new tree
/// # Arguments
/// * `start` - Starting value for the tree
/// * `fixed` - If `true` the tree would only contain one element and won't be simplified.
fn new(start: i128, fixed: bool) -> Self {
Self { lo: 0, curr: start, hi: start, fixed }
}

fn reposition(&mut self) -> bool {
let interval = self.hi - self.lo;
let new_mid = self.lo + interval / 2i128;

if new_mid == self.curr {
false
} else {
self.curr = new_mid;
true
}
}

fn magnitude_greater(lhs: i128, rhs: i128) -> bool {
if lhs == 0 {
return false;
}
(lhs > rhs) ^ (lhs.is_negative())
}
}

impl ValueTree for IntValueTree {
type Value = i128;

fn current(&self) -> Self::Value {
self.curr
}

fn simplify(&mut self) -> bool {
if self.fixed || !Self::magnitude_greater(self.hi, self.lo) {
return false;
}
self.hi = self.curr;
self.reposition()
}

fn complicate(&mut self) -> bool {
if self.fixed || !Self::magnitude_greater(self.hi, self.lo) {
return false;
}

self.lo = if self.curr != i128::MIN && self.curr != i128::MAX {
self.curr + if self.hi.is_negative() { -1i128 } else { 1i128 }
} else {
self.curr
};

self.reposition()
}
}

/// Value tree for signed ints (up to int256).
/// The strategy combines 3 different strategies, each assigned a specific weight:
/// Strategy for signed ints (up to i128).
/// The strategy combines 2 different strategies, each assigned a specific weight:
/// 1. Generate purely random value in a range. This will first choose bit size uniformly (up `bits`
/// param). Then generate a value for this bit size.
/// 2. Generate a random value around the edges (+/- 3 around min, 0 and max possible value)
/// 3. Generate a value from a predefined fixtures set
///
/// To define int fixtures:
/// - return an array of possible values for a parameter named `amount` declare a function `function
/// fixture_amount() public returns (int32[] memory)`.
/// - use `amount` named parameter in fuzzed test in order to include fixtures in fuzzed values
/// `function testFuzz_int32(int32 amount)`.
///
/// If fixture is not a valid int type then error is raised and random value generated.
#[derive(Debug)]
pub struct IntStrategy {
/// Bit size of int (e.g. 256)
Expand All @@ -101,9 +21,8 @@ pub struct IntStrategy {

impl IntStrategy {
/// Create a new strategy.
/// #Arguments
/// * `bits` - Size of uint in bits
/// * `fixtures` - A set of fixed values to be generated (according to fixtures weight)
/// # Arguments
/// * `bits` - Size of int in bits
pub fn new(bits: usize) -> Self {
Self { bits, edge_weight: 10usize, random_weight: 50usize }
}
Expand All @@ -121,14 +40,14 @@ impl IntStrategy {
3 => self.type_max() - offset,
_ => unreachable!(),
};
Ok(IntValueTree::new(start, false))
Ok(proptest::num::i128::BinarySearch::new(start))
}

fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
let rng = runner.rng();

let start: i128 = rng.gen_range(self.type_min()..=self.type_max());
Ok(IntValueTree::new(start, false))
Ok(proptest::num::i128::BinarySearch::new(start))
}

fn type_max(&self) -> i128 {
Expand All @@ -149,7 +68,7 @@ impl IntStrategy {
}

impl Strategy for IntStrategy {
type Tree = IntValueTree;
type Tree = proptest::num::i128::BinarySearch;
type Value = i128;

fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
Expand All @@ -162,24 +81,3 @@ impl Strategy for IntStrategy {
}
}
}

#[cfg(test)]
mod tests {
use crate::strategies::int::IntValueTree;
use proptest::strategy::ValueTree;

#[test]
fn test_int_tree_complicate_should_not_overflow() {
let mut int_tree = IntValueTree::new(i128::MAX, false);
assert_eq!(int_tree.hi, i128::MAX);
assert_eq!(int_tree.curr, i128::MAX);
int_tree.complicate();
assert_eq!(int_tree.lo, i128::MAX);

let mut int_tree = IntValueTree::new(i128::MIN, false);
assert_eq!(int_tree.hi, i128::MIN);
assert_eq!(int_tree.curr, i128::MIN);
int_tree.complicate();
assert_eq!(int_tree.lo, i128::MIN);
}
}
102 changes: 9 additions & 93 deletions tooling/fuzzer/src/strategies/uint.rs
Original file line number Diff line number Diff line change
@@ -1,82 +1,14 @@
use proptest::{
strategy::{NewTree, Strategy, ValueTree},
strategy::{NewTree, Strategy},
test_runner::TestRunner,
};
use rand::Rng;

/// Value tree for unsigned ints (up to uint256).
pub struct UintValueTree {
/// Lower base
lo: u128,
/// Current value
curr: u128,
/// Higher base
hi: u128,
/// If true cannot be simplified or complexified
fixed: bool,
}

impl UintValueTree {
/// Create a new tree
/// # Arguments
/// * `start` - Starting value for the tree
/// * `fixed` - If `true` the tree would only contain one element and won't be simplified.
fn new(start: u128, fixed: bool) -> Self {
Self { lo: 0, curr: start, hi: start, fixed }
}

fn reposition(&mut self) -> bool {
let interval = self.hi - self.lo;
let new_mid = self.lo + interval / 2;

if new_mid == self.curr {
false
} else {
self.curr = new_mid;
true
}
}
}

impl ValueTree for UintValueTree {
type Value = u128;

fn current(&self) -> Self::Value {
self.curr
}

fn simplify(&mut self) -> bool {
if self.fixed || (self.hi <= self.lo) {
return false;
}
self.hi = self.curr;
self.reposition()
}

fn complicate(&mut self) -> bool {
if self.fixed || (self.hi <= self.lo) {
return false;
}

self.lo = self.curr.wrapping_add(1);
self.reposition()
}
}

/// Value tree for unsigned ints (up to uint256).
/// The strategy combines 3 different strategies, each assigned a specific weight:
/// Value tree for unsigned ints (up to u128).
/// The strategy combines 2 different strategies, each assigned a specific weight:
/// 1. Generate purely random value in a range. This will first choose bit size uniformly (up `bits`
/// param). Then generate a value for this bit size.
/// 2. Generate a random value around the edges (+/- 3 around 0 and max possible value)
/// 3. Generate a value from a predefined fixtures set
///
/// To define uint fixtures:
/// - return an array of possible values for a parameter named `amount` declare a function `function
/// fixture_amount() public returns (uint32[] memory)`.
/// - use `amount` named parameter in fuzzed test in order to include fixtures in fuzzed values
/// `function testFuzz_uint32(uint32 amount)`.
///
/// If fixture is not a valid uint type then error is raised and random value generated.
#[derive(Debug)]
pub struct UintStrategy {
/// Bit size of uint (e.g. 256)
Expand All @@ -90,9 +22,8 @@ pub struct UintStrategy {

impl UintStrategy {
/// Create a new strategy.
/// #Arguments
/// # Arguments
/// * `bits` - Size of uint in bits
/// * `fixtures` - A set of fixed values to be generated (according to fixtures weight)
pub fn new(bits: usize) -> Self {
Self { bits, edge_weight: 10usize, random_weight: 50usize }
}
Expand All @@ -103,14 +34,14 @@ impl UintStrategy {
let is_min = rng.gen_bool(0.5);
let offset = rng.gen_range(0..4);
let start = if is_min { offset } else { self.type_max().saturating_sub(offset) };
Ok(UintValueTree::new(start, false))
Ok(proptest::num::u128::BinarySearch::new(start))
}

fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
let rng = runner.rng();
let start: u128 = rng.gen_range(0..=self.type_max());
let start = rng.gen_range(0..=self.type_max());

Ok(UintValueTree::new(start, false))
Ok(proptest::num::u128::BinarySearch::new(start))
}

fn type_max(&self) -> u128 {
Expand All @@ -123,30 +54,15 @@ impl UintStrategy {
}

impl Strategy for UintStrategy {
type Tree = UintValueTree;
type Tree = proptest::num::u128::BinarySearch;
type Value = u128;
fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
let total_weight = self.random_weight + self.edge_weight;
let bias = runner.rng().gen_range(0..total_weight);
// randomly select one of 3 strategies
// randomly select one of 2 strategies
match bias {
x if x < self.edge_weight => self.generate_edge_tree(runner),
_ => self.generate_random_tree(runner),
}
}
}

#[cfg(test)]
mod tests {
use crate::strategies::uint::UintValueTree;
use proptest::strategy::ValueTree;

#[test]
fn test_uint_tree_complicate_max() {
let mut uint_tree = UintValueTree::new(u128::MAX, false);
assert_eq!(uint_tree.hi, u128::MAX);
assert_eq!(uint_tree.curr, u128::MAX);
uint_tree.complicate();
assert_eq!(uint_tree.lo, u128::MIN);
}
}

0 comments on commit 976c62c

Please sign in to comment.