Skip to content
This repository has been archived by the owner on Aug 24, 2021. It is now read-only.

Commit

Permalink
feat: Add arbitrary ints (with shrinking support) (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcornaz authored Apr 1, 2020
1 parent 40ef33d commit 795201e
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package com.github.jcornaz.kwik.fuzzer.api.simplifier

import com.github.jcornaz.kwik.fuzzer.api.ExperimentalKwikFuzzer

/**
* Returns a simplifier that use the given [simplify] function to find simpler values
*/
@ExperimentalKwikFuzzer
internal fun <T> simplifier(simplify: (T) -> Sequence<T>): Simplifier<T> = object :
Simplifier<T> {
fun <T> simplifier(simplify: (T) -> Sequence<T>): Simplifier<T> = object : Simplifier<T> {
override fun simplify(value: T): Sequence<T> = simplify(value)
}

Expand Down
10 changes: 10 additions & 0 deletions fuzzer/stdlib/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
kotlin {
sourceSets {
commonMain {
dependencies {
api(project(":fuzzer-api"))
implementation(project(":generator-stdlib"))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.github.jcornaz.kwik.fuzzer.stdlib

import com.github.jcornaz.kwik.fuzzer.api.Arbitrary
import com.github.jcornaz.kwik.fuzzer.api.ExperimentalKwikFuzzer
import com.github.jcornaz.kwik.fuzzer.api.Fuzzer
import com.github.jcornaz.kwik.fuzzer.api.simplifier.Simplifier
import com.github.jcornaz.kwik.fuzzer.api.simplifier.filter
import com.github.jcornaz.kwik.fuzzer.api.toFuzzer
import com.github.jcornaz.kwik.fuzzer.stdlib.simplifier.int
import com.github.jcornaz.kwik.generator.api.Generator
import com.github.jcornaz.kwik.generator.stdlib.ints

/**
* Returns a [Fuzzer] generating [Int] inputs that are between the given [min] anx [max] values.
*
* The generated inputs have high probability to be [Int.MIN_VALUE], [Int.MAX_VALUE], `-1`, `1` or `0`
*/
@ExperimentalKwikFuzzer
fun Arbitrary.int(min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE): Fuzzer<Int> {
val range = min..max

return Generator.ints(min, max)
.toFuzzer(Simplifier.int.filter { it in range })
}

/**
* Returns a [Fuzzer] generating [Int] inputs.
*
* The generated inputs have high probability to be [Int.MIN_VALUE], [Int.MAX_VALUE], `-1`, `1` or `0`
*/
@ExperimentalKwikFuzzer
fun Arbitrary.int(): Fuzzer<Int> = anyInt

@ExperimentalKwikFuzzer
private val anyInt: Fuzzer<Int> by lazy { Generator.ints().toFuzzer(Simplifier.int) }
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.github.jcornaz.kwik.fuzzer.stdlib.simplifier

import com.github.jcornaz.kwik.fuzzer.api.ExperimentalKwikFuzzer
import com.github.jcornaz.kwik.fuzzer.api.simplifier.Simplifier
import kotlin.math.absoluteValue

/**
* Returns a simplifier capable of simplifying [Int].
*
* zero is the simplest value.
* the closest to zero, the simpler.
* positive values are also considered simpler than their negative counterpart.
*/
@ExperimentalKwikFuzzer
val Simplifier.Companion.int: Simplifier<Int>
get() = IntSimplifier

@ExperimentalKwikFuzzer
private object IntSimplifier : Simplifier<Int> {
override fun simplify(value: Int): Sequence<Int> = when (value) {
0 -> emptySequence()
1 -> sequenceOf(0)
-1 -> sequenceOf(0, 1)
else -> sequence {
yield(value / 2)

if (value < 0) yield(value.absoluteValue)

if (value.absoluteValue > 2)
yield(if (value < 0) value + 1 else value - 1)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.github.jcornaz.kwik.fuzzer.stdlib

import com.github.jcornaz.kwik.fuzzer.api.Arbitrary
import com.github.jcornaz.kwik.fuzzer.api.ExperimentalKwikFuzzer
import com.github.jcornaz.kwik.fuzzer.api.simplifier.Simplifier
import com.github.jcornaz.kwik.fuzzer.api.simplifier.filter
import com.github.jcornaz.kwik.fuzzer.stdlib.simplifier.int
import com.github.jcornaz.kwik.generator.api.Generator
import com.github.jcornaz.kwik.generator.api.randomSequence
import com.github.jcornaz.kwik.generator.stdlib.ints
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

@ExperimentalKwikFuzzer
class ArbitraryIntTest {

@Test
fun usesIntGenerator() {
assertEquals(
Generator.ints(32, 54).drawSample(),
Arbitrary.int(32, 54).generator.drawSample()
)
}

@Test
fun hasSameDefaultsThanGenerator() {
assertEquals(
Generator.ints().drawSample(),
Arbitrary.int().generator.drawSample()
)
}

@Test
fun hasSameDefaultMinThanGenerator() {
assertEquals(
Generator.ints(max = 34).drawSample(),
Arbitrary.int(max = 34).generator.drawSample()
)
}

@Test
fun hasSameDefaultMaxThanGenerator() {
assertEquals(
Generator.ints(min = 34).drawSample(),
Arbitrary.int(min = 34).generator.drawSample()
)
}

@Test
fun doesNotHaveAnyGuarantee() {
assertTrue(Arbitrary.int().guarantees.isEmpty())
}

@Test
fun useIntSimplifier() {
repeat(1000) {
val initialValue = Random.nextInt()
assertEquals(
Simplifier.int.simplify(initialValue).toList(),
Arbitrary.int().simplifier.simplify(initialValue).toList()
)
}
}

@Test
fun simplifierIsFiltered() {
Generator.create { it.nextInt(-100, 100) }
.randomSequence(0)
.take(1000)
.forEach { initialValue ->
assertEquals(
Simplifier.int.filter { it in 0..100 }.simplify(initialValue).toList(),
Arbitrary.int(0, 100).simplifier.simplify(initialValue).toList()
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.github.jcornaz.kwik.fuzzer.stdlib

import com.github.jcornaz.kwik.generator.api.Generator
import com.github.jcornaz.kwik.generator.api.randomSequence


fun <T> Generator<T>.drawSample() = randomSequence(0).take(100).toList()
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.github.jcornaz.kwik.fuzzer.stdlib.simplifier

import com.github.jcornaz.kwik.fuzzer.api.ExperimentalKwikFuzzer
import com.github.jcornaz.kwik.fuzzer.api.simplifier.Simplifier
import com.github.jcornaz.kwik.fuzzer.api.simplifier.findSimplestFalsification
import com.github.jcornaz.kwik.generator.api.*
import kotlin.math.abs
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

@ExperimentalKwikFuzzer
class IntSimplifierTest {
private val anyInt = Generator.create(Random::nextInt)
private val anyNegativeInt = Generator.create { it.nextInt(Int.MIN_VALUE, -1) }

@Test
fun zeroIsTheSimplestValue() {
assertTrue(Simplifier.int.simplify(0).none())
}

@Test
fun zeroIsSimplerThanOne() {
assertEquals(0, Simplifier.int.simplify(1).single())
}

@Test
fun zeroIsSimplerThanMinusOne() {
assertTrue(Simplifier.int.simplify(-1).any { it == 0 })
}

@Test
fun positiveIsSimplerThanNegative() {
val simplifier = Simplifier.int

Generator.frequency(
1.0 to Generator.of(-2, -1),
2.0 to anyNegativeInt
)
.randomSequence(0)
.take(200)
.forEach { value ->
assertTrue(simplifier.simplify(value).any { it == abs(value) })
}
}

@Test
fun nonZeroValueHaveSimplerValues() {
Generator.frequency(
1.0 to Generator.of(-2, -1, 1, 2),
2.0 to anyInt.filterNot { it == 0 }
)
.randomSequence(0)
.take(200)
.forEach { value ->
assertTrue(Simplifier.int.simplify(value).any())
}
}

@Test
fun simplerValuesAreCloserToZero() {
Generator.frequency(
1.0 to Generator.of(-2, -1, 1, 2),
2.0 to anyInt.filterNot { it == 0 }
)
.randomSequence(0)
.take(200)
.forEach { value ->
assertTrue(Simplifier.int.simplify(value).all {
(it == abs(value)) || (abs(it) < abs(value))
})
}
}

@Test
fun returnsDistinctSequence() {
Generator.frequency(
2.0 to Generator.of((-2)..2),
1.0 to anyInt
)
.randomSequence(0)
.take(200)
.forEach { value ->
val set = HashSet<Int>()
assertTrue(Simplifier.int.simplify(value).all(set::add))
}
}

@Test
fun allowToFindSimplerFalsifyingValue() {
val passRange = (-42)..1337

Generator.create { it.nextInt(Int.MIN_VALUE, passRange.first - 1) }
.plus(Generator.create { it.nextInt(passRange.last + 1, Int.MAX_VALUE) })
.randomSequence(0)
.take(200)
.forEach { initialValue ->
val simplestValue =
Simplifier.int
.findSimplestFalsification(initialValue) { it in passRange }

if (initialValue < 0) {
assertEquals(-43, simplestValue)
} else {
assertEquals(1338, simplestValue)
}
}
}
}
5 changes: 4 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ include("generator-api")
include("generator-stdlib")
include("generator-test")

include("evaluator")
include("fuzzer-api")
include("fuzzer-stdlib")

include("evaluator")

project(":generator-api").projectDir = file("generator/api")
project(":generator-stdlib").projectDir = file("generator/stdlib")
project(":generator-test").projectDir = file("generator/test")

project(":fuzzer-api").projectDir = file("fuzzer/api")
project(":fuzzer-stdlib").projectDir = file("fuzzer/stdlib")

0 comments on commit 795201e

Please sign in to comment.