Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some misc fuzzers #9

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
293 changes: 215 additions & 78 deletions lib/aiken/fuzz.ak
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use aiken/builtin
use aiken/bytearray
use aiken/hash.{blake2b_256}
use aiken/list
use aiken/math

// Internal

Expand Down Expand Up @@ -95,11 +98,12 @@ pub fn bool() -> Fuzzer<Bool> {
}

pub fn bytearray() -> Fuzzer<ByteArray> {
fail
bytearray_between(0, 1024)
}

pub fn bytearray_between(_min: Int, _max: Int) -> Fuzzer<ByteArray> {
fail
pub fn bytearray_between(min: Int, max: Int) -> Fuzzer<ByteArray> {
let bytes <- and_then(list_between(byte(), min, max))
constant(list.foldl(bytes, #"", bytearray.concat))
}

/// Generate a random integer value. It favors small values near zero, but generate across the whole range [-2^64; 2^64 - 1]
Expand All @@ -113,33 +117,33 @@ pub fn int() -> Fuzzer<Int> {
Some((Seeded { seed: builtin.blake2b_256(seed), choices }, choice))
}
|> fn(return) {
if fst_choice < 128 {
return(fst_choice, builtin.cons_bytearray(fst_choice, choices))
} else if fst_choice < 224 {
return(
fst_choice % 16,
builtin.cons_bytearray(fst_choice % 16, choices),
)
} else if fst_choice < 236 {
let snd_choice = builtin.index_bytearray(seed, 1)
return(
-snd_choice,
builtin.cons_bytearray(
snd_choice,
builtin.cons_bytearray(fst_choice, choices),
),
)
} else {
let snd_choice = builtin.index_bytearray(seed, 1)
return(
u16(fst_choice, snd_choice),
builtin.cons_bytearray(
snd_choice,
builtin.cons_bytearray(fst_choice, choices),
),
)
}
}
if fst_choice < 128 {
return(fst_choice, builtin.cons_bytearray(fst_choice, choices))
} else if fst_choice < 224 {
return(
fst_choice % 16,
builtin.cons_bytearray(fst_choice % 16, choices),
)
} else if fst_choice < 236 {
let snd_choice = builtin.index_bytearray(seed, 1)
return(
-snd_choice,
builtin.cons_bytearray(
snd_choice,
builtin.cons_bytearray(fst_choice, choices),
),
)
} else {
let snd_choice = builtin.index_bytearray(seed, 1)
return(
u16(fst_choice, snd_choice),
builtin.cons_bytearray(
snd_choice,
builtin.cons_bytearray(fst_choice, choices),
),
)
}
}
}

Replayed { cursor, choices } ->
Expand All @@ -166,6 +170,18 @@ pub fn int() -> Fuzzer<Int> {
}
}

pub fn positive_int() -> Fuzzer<Int> {
int() |> map(math.abs) |> map(fn(x) { x + 1 })
}

pub fn nonnegative_int() -> Fuzzer<Int> {
int() |> map(math.abs)
}

pub fn negative_int() -> Fuzzer<Int> {
int() |> map(fn(x) { -math.abs(x + 1) })
}

pub fn int_between(min: Int, max: Int) -> Fuzzer<Int> {
if max < min {
int_between(max, min)
Expand All @@ -176,19 +192,117 @@ pub fn int_between(min: Int, max: Int) -> Fuzzer<Int> {
let delta = ( max - min ) / 2
int()
|> and_then(
fn(lo) {
int()
|> map(fn(hi) { mid - lo % ( delta + 1 ) + hi % ( delta + 1 ) })
},
)
fn(lo) {
int()
|> map(fn(hi) { mid - lo % ( delta + 1 ) + hi % ( delta + 1 ) })
},
)
}
}

pub fn uniform(bits: Int) -> Fuzzer<Int> {
// TODO: switch to Han-Hoshi for better uniform, and support min/max?
if bits == 0 {
constant(0)
} else {
let bit <- and_then(bool())
let rest <- and_then(uniform(bits - 1))
if bit {
constant(rest * 2 + 1)
} else {
constant(rest * 2)
}
}
}

pub fn byte() -> Fuzzer<ByteArray> {
let byte <- and_then(int_between(0, 255))
constant(bytearray.push(#"", byte))
}

pub fn hash() -> Fuzzer<ByteArray> {
let b <- and_then(byte())
let hash = blake2b_256(b)
constant(bytearray.take(hash, 28))
}

/// Generate a random list of elements from a given element.
pub fn list(fuzzer: Fuzzer<a>) -> Fuzzer<List<a>> {
list_between(fuzzer, 0, 32)
}

pub fn nonempty_list(fuzzer: Fuzzer<a>) -> Fuzzer<List<a>> {
list_between(fuzzer, 1, 32)
}

pub fn sorted(
fuzzer: Fuzzer<List<a>>,
compare: fn(a, a) -> Ordering,
) -> Fuzzer<List<a>> {
let ls <- and_then(fuzzer)
constant(list.sort(ls, compare))
}

pub fn list_with_elem(fuzzer: Fuzzer<a>) -> Fuzzer<(List<a>, a)> {
let xs <- and_then(nonempty_list(fuzzer))
let x <- and_then(one_of(xs))
constant((xs, x))
}

pub fn list_with_subset(fuzzer: Fuzzer<a>) -> Fuzzer<(List<a>, List<a>)> {
let xs <- and_then(list(fuzzer))
let bits <- and_then(list_between(bool(), 0, list.length(xs)))
let ys =
list.filter(
list.zip(xs, bits),
fn(pair) {
let (_, b) = pair
b
},
)
let (ys, _) = list.unzip(ys)
constant((xs, ys))
}

pub fn filter(fuzzer: Fuzzer<a>, predicate: fn(a) -> Bool) -> Fuzzer<a> {
do_filter(fuzzer, predicate, 100)
}

fn do_filter(
fuzzer: Fuzzer<a>,
predicate: fn(a) -> Bool,
max_tries: Int,
) -> Fuzzer<a> {
if max_tries <= 0 {
fail
} else {
let x <- and_then(fuzzer)
if predicate(x) {
constant(x)
} else {
do_filter(fuzzer, predicate, max_tries - 1)
}
}
}

pub fn distinct(fuzzer: Fuzzer<List<a>>) -> Fuzzer<List<a>> {
let xs <- and_then(fuzzer)
constant(dedup(xs, []))
}

fn dedup(xs: List<a>, seen: List<a>) -> List<a> {
when xs is {
[] ->
[]
[x, ..xs] ->
if list.has(seen, x) {
dedup(xs, seen)
} else {
[x, ..dedup(xs, [x, ..seen])]
}
}
}

/// Generate a random list of elements with length within specified bounds.
pub fn list_between(fuzzer: Fuzzer<a>, min: Int, max: Int) -> Fuzzer<List<a>> {
if min > max {
Expand All @@ -212,38 +326,38 @@ fn do_list_between(avg, fuzzer, min, max, length, xs) -> Fuzzer<List<a>> {
with_choice(min_rand)
|> and_then(always(fuzzer, _))
|> and_then(
fn(x) {
do_list_between(avg, fuzzer, min, max, length + 1, [x, ..xs])
},
)
fn(x) {
do_list_between(avg, fuzzer, min, max, length + 1, [x, ..xs])
},
)
} else if length >= max {
with_choice(max_rand)
|> map(fn(_) { xs })
} else {
rand
|> and_then(
fn(n) {
// This is the probability above but simplified to use only
// multiplications since division on-chain is expensive.
if n + n * avg <= max_rand * avg {
fuzzer
|> and_then(
fn(x) {
do_list_between(
avg,
fuzzer,
min,
max,
length + 1,
[x, ..xs],
)
},
)
} else {
constant(xs)
}
},
)
fn(n) {
// This is the probability above but simplified to use only
// multiplications since division on-chain is expensive.
if n + n * avg <= max_rand * avg {
fuzzer
|> and_then(
fn(x) {
do_list_between(
avg,
fuzzer,
min,
max,
length + 1,
[x, ..xs],
)
},
)
} else {
constant(xs)
}
},
)
}
}

Expand All @@ -253,32 +367,55 @@ fn do_list_between(avg, fuzzer, min, max, length, xs) -> Fuzzer<List<a>> {
pub fn one_of(xs: List<a>) -> Fuzzer<a> {
let len = list.length(xs)
expect len > 0
int_between(0, len - 1)
uniform(math.log(len, 2) + 1)
|> map(
fn(ix: Int) {
expect Some(item) = list.at(xs, ix)
item
},
)
fn(ix: Int) {
expect Some(item) = list.at(xs, ix % len)
item
},
)
}

// Combining Fuzzers

pub fn either(_fuzz_a: Fuzzer<a>, _fuzz_b: Fuzzer<a>) -> Fuzzer<a> {
fail
pub fn either(fuzz_a: Fuzzer<a>, fuzz_b: Fuzzer<a>) -> Fuzzer<a> {
bool()
|> and_then(
fn(b) {
if b {
fuzz_a
} else {
fuzz_b
}
},
)
}

pub fn ordered_pair(
fuzz_a: Fuzzer<a>,
fuzz_b: Fuzzer<a>,
compare: fn(a, a) -> Ordering,
) -> Fuzzer<(a, a)> {
let a <- and_then(fuzz_a)
let b <- and_then(fuzz_b)
if compare(a, b) == Greater {
constant((b, a))
} else {
constant((a, b))
}
}

pub fn option(fuzz_a: Fuzzer<a>) -> Fuzzer<Option<a>> {
bool()
|> and_then(
fn(predicate) {
if predicate {
fuzz_a |> map(Some)
} else {
constant(None)
}
},
)
fn(predicate) {
if predicate {
fuzz_a |> map(Some)
} else {
constant(None)
}
},
)
}

/// Combine a [Fuzzer](https://aiken-lang.github.io/prelude/aiken.html#Fuzzer) with the result of a another one.
Expand Down Expand Up @@ -608,6 +745,6 @@ pub fn map9(

pub fn label(str: String) -> Void {
str
|> builtin.append_string(@"", _)
|> builtin.append_string(@"\0", _)
|> builtin.debug(Void)
}
Loading
Loading