Skip to content

Commit

Permalink
Remove breaking edgecase changes
Browse files Browse the repository at this point in the history
  • Loading branch information
BartvHelvert committed Jun 13, 2021
1 parent be3a359 commit 0f46027
Show file tree
Hide file tree
Showing 36 changed files with 260 additions and 291 deletions.
19 changes: 8 additions & 11 deletions kotest-property/src/commonMain/kotlin/io/kotest/property/Gen.kt
Expand Up @@ -27,7 +27,7 @@ sealed class Gen<out A> {
* Returns values from this generator as a lazily generated sequence.
*
* If this gen is an [Arb], then each value will either be a sample or an edge case. The bias
* towards edge cases or samples is given by the value of [EdgeConfig.edgeCasesGenerationProbability]
* towards edge cases or samples is given by the value of [EdgeConfig.edgecasesGenerationProbability]
* inside the [edgeConfig] parameter.
*
* If this gen is an [Exhaustive], then the returned values will iterate in turn, repeating
Expand All @@ -42,9 +42,9 @@ sealed class Gen<out A> {
is Arb -> {
val samples = this.samples(rs).iterator()
generateSequence {
val isEdgeCase = rs.random.nextDouble(0.0, 1.0) < edgeConfig.edgeCasesGenerationProbability
val isEdgeCase = rs.random.nextDouble(0.0, 1.0) < edgeConfig.edgecasesGenerationProbability
if (isEdgeCase) {
this.edgeCase(rs)?.asSample() ?: samples.next()
this.edgecase(rs)?.asSample() ?: samples.next()
} else samples.next()
}
}
Expand All @@ -67,7 +67,7 @@ sealed class Gen<out A> {
/**
* An [Arb] (short for arbitrary) is a generator of values in two categories: edge cases and samples.
*
* Edgecases are values that are a common source of bugs. For example, a function using ints is
* Edge cases are values that are a common source of bugs. For example, a function using ints is
* more likely to fail for common edge cases like zero, minus 1, positive 1, [Int.MAX_VALUE] and [Int.MIN_VALUE]
* rather than random values like 965489. Therefore it is useful that we try to include such values
* rather than relying entirely on random values which are unlikely to generate these.
Expand All @@ -90,10 +90,7 @@ abstract class Arb<out A> : Gen<A>() {
* If this arb provides multiple edge cases, then one should be chosen randomly.
* Can return null if no edge cases are available.
*/
abstract fun edgeCase(rs: RandomSource): A?

@Deprecated("use edgeCase", ReplaceWith("edgeCase(rs)"))
open fun edgecase(rs: RandomSource): A? = edgeCase(rs)
abstract fun edgecase(rs: RandomSource): A?

/**
* Returns a single random [Sample] from this [Arb] using the supplied random source.
Expand Down Expand Up @@ -156,13 +153,13 @@ fun <A> A.asSample(): Sample<A> = Sample(this)
fun <A> sampleOf(a: A, shrinker: Shrinker<A>) = Sample(a, shrinker.rtree(a))

data class EdgeConfig(
val edgeCasesGenerationProbability: Double = PropertyTesting.edgeCasesGenerationProbability
val edgecasesGenerationProbability: Double = PropertyTesting.edgecasesGenerationProbability
) {
companion object;

init {
check(edgeCasesGenerationProbability in 0.0..1.0) {
"provided edgeCasesProbability $edgeCasesGenerationProbability is not between 0.0 and 1.0"
check(edgecasesGenerationProbability in 0.0..1.0) {
"provided edgecasesProbability $edgecasesGenerationProbability is not between 0.0 and 1.0"
}
}
}
Expand Up @@ -6,7 +6,7 @@ import io.kotest.property.Sample

/**
* Returns a sequence of size [count] from values generated from this arb.
* Edgecases will be ignored.
* Edge cases will be ignored.
*/
fun <A> Arb<A>.take(count: Int, rs: RandomSource = RandomSource.default()): Sequence<A> =
samples(rs).map { it.value }.take(count)
Expand All @@ -31,7 +31,7 @@ fun <A> Arb.Companion.lazy(f: () -> Arb<A>): Arb<A> {
val arb by kotlin.lazy { f() }

return object : Arb<A>() {
override fun edgeCase(rs: RandomSource): A? = arb.edgeCase(rs)
override fun edgecase(rs: RandomSource): A? = arb.edgecase(rs)
override fun sample(rs: RandomSource): Sample<A> = arb.sample(rs)
}
}
Expand Up @@ -420,22 +420,22 @@ private fun <A, B, C, D, E, F, G, H, I, J, K, L, M, N, T> Arb.Companion.bindN(

return object : Arb<T>() {

override fun edgeCase(rs: RandomSource): T? {
override fun edgecase(rs: RandomSource): T? {
return bindFn(
arbA.edgeCase(rs) ?: arbA.next(rs),
arbB.edgeCase(rs) ?: arbB.next(rs),
arbC.edgeCase(rs) ?: arbC.next(rs),
arbD.edgeCase(rs) ?: arbD.next(rs),
arbE.edgeCase(rs) ?: arbE.next(rs),
arbF.edgeCase(rs) ?: arbF.next(rs),
arbG.edgeCase(rs) ?: arbG.next(rs),
arbH.edgeCase(rs) ?: arbH.next(rs),
arbI.edgeCase(rs) ?: arbI.next(rs),
arbJ.edgeCase(rs) ?: arbJ.next(rs),
arbK.edgeCase(rs) ?: arbK.next(rs),
arbL.edgeCase(rs) ?: arbL.next(rs),
arbM.edgeCase(rs) ?: arbM.next(rs),
arbN.edgeCase(rs) ?: arbN.next(rs),
arbA.edgecase(rs) ?: arbA.next(rs),
arbB.edgecase(rs) ?: arbB.next(rs),
arbC.edgecase(rs) ?: arbC.next(rs),
arbD.edgecase(rs) ?: arbD.next(rs),
arbE.edgecase(rs) ?: arbE.next(rs),
arbF.edgecase(rs) ?: arbF.next(rs),
arbG.edgecase(rs) ?: arbG.next(rs),
arbH.edgecase(rs) ?: arbH.next(rs),
arbI.edgecase(rs) ?: arbI.next(rs),
arbJ.edgecase(rs) ?: arbJ.next(rs),
arbK.edgecase(rs) ?: arbK.next(rs),
arbL.edgecase(rs) ?: arbL.next(rs),
arbM.edgecase(rs) ?: arbM.next(rs),
arbN.edgecase(rs) ?: arbN.next(rs),
)
}

Expand Down
Expand Up @@ -13,41 +13,41 @@ fun <A> arbitrary(fn: (RandomSource) -> A): Arb<A> =
* Creates a new [Arb] that performs no shrinking, uses the given edge cases and
* generates values from the given function.
*/
fun <A> arbitrary(edgeCases: List<A>, fn: (RandomSource) -> A): Arb<A> = object : Arb<A>() {
override fun edgeCase(rs: RandomSource): A? = if (edgeCases.isEmpty()) null else edgeCases.random(rs.random)
fun <A> arbitrary(edgecases: List<A>, fn: (RandomSource) -> A): Arb<A> = object : Arb<A>() {
override fun edgecase(rs: RandomSource): A? = if (edgecases.isEmpty()) null else edgecases.random(rs.random)
override fun sample(rs: RandomSource): Sample<A> = Sample(fn(rs))
}

/**
* Creates a new [Arb] that performs shrinking using the supplied [Shrinker], uses the given edge cases and
* generates values from the given function.
*/
fun <A> arbitrary(edgeCases: List<A>, shrinker: Shrinker<A>, fn: (RandomSource) -> A): Arb<A> = object : Arb<A>() {
override fun edgeCase(rs: RandomSource): A? = if (edgeCases.isEmpty()) null else edgeCases.random(rs.random)
fun <A> arbitrary(edgecases: List<A>, shrinker: Shrinker<A>, fn: (RandomSource) -> A): Arb<A> = object : Arb<A>() {
override fun edgecase(rs: RandomSource): A? = if (edgecases.isEmpty()) null else edgecases.random(rs.random)
override fun sample(rs: RandomSource): Sample<A> = sampleOf(fn(rs), shrinker)
}

/**
* Creates a new [Arb] that generates edge cases from the given [edgeCaseFn] function
* Creates a new [Arb] that generates edge cases from the given [edgecaseFn] function
* and generates samples from the given [sampleFn] function.
*/
fun <A> arbitrary(edgeCaseFn: (RandomSource) -> A?, sampleFn: (RandomSource) -> A): Arb<A> =
fun <A> arbitrary(edgecaseFn: (RandomSource) -> A?, sampleFn: (RandomSource) -> A): Arb<A> =
object : Arb<A>() {
override fun edgeCase(rs: RandomSource): A? = edgeCaseFn(rs)
override fun edgecase(rs: RandomSource): A? = edgecaseFn(rs)
override fun sample(rs: RandomSource): Sample<A> = Sample(sampleFn(rs))
}

/**
* Creates a new [Arb] that generates edge cases from the given [edgeCaseFn] function,
* Creates a new [Arb] that generates edge cases from the given [edgecaseFn] function,
* performs shrinking using the supplied [Shrinker, and generates samples from the given [sampleFn] function.
*/
fun <A> arbitrary(
edgeCaseFn: (RandomSource) -> A?,
edgecaseFn: (RandomSource) -> A?,
shrinker: Shrinker<A>,
sampleFn: (RandomSource) -> A
): Arb<A> =
object : Arb<A>() {
override fun edgeCase(rs: RandomSource): A? = edgeCaseFn(rs)
override fun edgecase(rs: RandomSource): A? = edgecaseFn(rs)
override fun sample(rs: RandomSource): Sample<A> = sampleOf(sampleFn(rs), shrinker)
}

Expand Down
Expand Up @@ -18,7 +18,7 @@ internal const val MIN_SUPPLEMENTARY_CODE_POINT = 0x010000

fun Arb.Companion.codepoints(): Arb<Codepoint> =
Arb.int(MIN_CODE_POINT..MAX_CODE_POINT)
.withEdgeCases(MIN_CODE_POINT, MAX_CODE_POINT)
.withEdgecases(MIN_CODE_POINT, MAX_CODE_POINT)
.map { Codepoint(it) }

@Deprecated("Codepoints in Arb.{code point} are deprecated. Use Codepoint.{code point} instead.",
Expand Down Expand Up @@ -95,74 +95,74 @@ fun Arb.Companion.egyptianHieroglyphs(): Arb<Codepoint> =
fun Codepoint.Companion.az(): Arb<Codepoint> =
Arb.int('a'.code..'z'.code)
.map { Codepoint(it) }
.withEdgeCases(Codepoint('a'.code))
.withEdgecases(Codepoint('a'.code))

fun Codepoint.Companion.alphanumeric(): Arb<Codepoint> =
Arb.element((('a'..'z') + ('A'..'Z') + ('0'..'9')).toList()).map { Codepoint(it.code) }

fun Codepoint.Companion.ascii(): Arb<Codepoint> =
Arb.int(' '.code..'z'.code)
.map { Codepoint(it) }
.withEdgeCases(Codepoint('a'.code))
.withEdgecases(Codepoint('a'.code))

fun Codepoint.Companion.georgian(): Arb<Codepoint> {
val empty = listOf(0x10C6, 0x10ce, 0x10cf) + (0x10c8..0x10cC).toList()
return Arb.int(0x10A0..0x10FF)
.withEdgeCases(0x10A0)
.withEdgecases(0x10A0)
.filterNot { it in empty }
.map { Codepoint(it) }
}

fun Codepoint.Companion.katakana(): Arb<Codepoint> =
Arb.int(0x30A0..0x30FF)
.withEdgeCases(0x30A1)
.withEdgecases(0x30A1)
.map { Codepoint(it) }

fun Codepoint.Companion.greekCoptic(): Arb<Codepoint> {
val empty = (0x0380..0x0383).toList() + listOf(0x0378, 0x0379, 0x038B, 0x038D, 0x03A2)
return Arb.int(0x0370..0x03FF)
.withEdgeCases(0x03B1)
.withEdgecases(0x03B1)
.filterNot { it in empty }
.map { Codepoint(it) }
}

fun Codepoint.Companion.armenian(): Arb<Codepoint> {
val empty = listOf(0x0557, 0x0558, 0x058B, 0x058C)
return Arb.int(0x0531..0x058F)
.withEdgeCases(0x0531)
.withEdgecases(0x0531)
.filterNot { it in empty }
.map { Codepoint(it) }
}

fun Codepoint.Companion.hebrew(): Arb<Codepoint> {
val empty = (0x05c8..0x05cF).toList() + (0x05eB..0x05eE).toList()
return Arb.int(0x0591..0x05F4)
.withEdgeCases(0x05D0)
.withEdgecases(0x05D0)
.filterNot { it in empty }
.map { Codepoint(it) }
}

fun Codepoint.Companion.arabic(): Arb<Codepoint> {
val empty = listOf(0x062D)
return Arb.int(0x0600..0x06FF)
.withEdgeCases(0x0627)
.withEdgecases(0x0627)
.filterNot { it in empty }
.map { Codepoint(it) }
}

fun Codepoint.Companion.cyrillic(): Arb<Codepoint> =
Arb.int(0x0400..0x04FF).withEdgeCases(0x0430).map { Codepoint(it) }
Arb.int(0x0400..0x04FF).withEdgecases(0x0430).map { Codepoint(it) }

fun Codepoint.Companion.hiragana(): Arb<Codepoint> {
val empty = listOf(0x3097, 0x3098)
return Arb.int(0x3041..0x309F)
.withEdgeCases(0x3041)
.withEdgecases(0x3041)
.filterNot { it in empty }
.map { Codepoint(it) }
}

fun Codepoint.Companion.egyptianHieroglyphs(): Arb<Codepoint> =
Arb.int(0x13000..0x1342E).withEdgeCases(0x13000).map { Codepoint(it) }
Arb.int(0x13000..0x1342E).withEdgecases(0x13000).map { Codepoint(it) }

data class Codepoint(val value: Int) {
companion object
Expand Down
Expand Up @@ -87,15 +87,15 @@ fun <A> Arb.Companion.list(gen: Gen<A>, range: IntRange = 0..100): Arb<List<A>>
check(range.first >= 0)

return arbitrary(
edgeCaseFn = { rs ->
edgecaseFn = { rs ->
val emptyList = emptyList<A>()
val singleList: List<A>? = when (gen) {
is Arb -> (gen.edgeCase(rs) ?: gen.next(rs))?.let { listOf(it) }
is Arb -> (gen.edgecase(rs) ?: gen.next(rs))?.let { listOf(it) }
is Exhaustive -> gen.values.firstOrNull()?.let { listOf(it) }
}
val repeatedList: List<A>? = when {
range.last < 2 -> null // too small for repeats
gen is Arb -> (gen.edgeCase(rs) ?: gen.next(rs))?.let { a -> List(max(2, range.first)) { a } }
gen is Arb -> (gen.edgecase(rs) ?: gen.next(rs))?.let { a -> List(max(2, range.first)) { a } }
gen is Exhaustive -> gen.values.firstOrNull()?.let { a -> List(max(2, range.first)) { a } }
else -> null
}
Expand Down
Expand Up @@ -84,7 +84,7 @@ fun <A> Arb.Companion.choose(a: Pair<Int, Arb<A>>, b: Pair<Int, Arb<A>>, vararg
}

return arbitrary(
edgeCaseFn = { allArbs.edgeCase(it) },
edgecaseFn = { allArbs.edgecase(it) },
sampleFn = { rs ->
val n = rs.random.nextInt(1, total + 1)
val arb = pick(n, allPairs)
Expand Down Expand Up @@ -122,7 +122,7 @@ fun <A> Arb.Companion.subsequence(list: List<A>): Arb<List<A>> = arbitrary {
fun <A> Arb.Companion.choice(arb: Arb<A>, vararg arbs: Arb<A>): Arb<A> {
val arbList = listOf(arb, *arbs)
return arbitrary(
edgeCaseFn = { arbList.edgeCase(it) },
edgecaseFn = { arbList.edgecase(it) },
sampleFn = { arbList.random(it.random).next(it) }
)
}
Expand All @@ -139,7 +139,7 @@ fun <A> Arb.Companion.choice(arb: Arb<A>, vararg arbs: Arb<A>): Arb<A> {
*/
fun <A> Arb.Companion.choice(arbs: List<Arb<A>>): Arb<A> {
return arbitrary(
edgeCaseFn = { arbs.edgeCase(it) },
edgecaseFn = { arbs.edgecase(it) },
sampleFn = { arbs.random(it.random).next(it) }
)
}
Expand Down
Expand Up @@ -10,56 +10,40 @@ import io.kotest.property.Sample
* If the chosen arb has no edge cases, then another arb will be chosen.
* If all [Arb]s have no edge cases, then returns null.
*/
tailrec fun <A> List<Arb<A>>.edgeCase(rs: RandomSource): A? {
tailrec fun <A> List<Arb<A>>.edgecase(rs: RandomSource): A? {
if (this.isEmpty()) return null
val shuffled = this.shuffled(rs.random)
return when (val edge = shuffled.first().edgeCase(rs)) {
null -> this.drop(1).edgeCase(rs)
return when (val edge = shuffled.first().edgecase(rs)) {
null -> this.drop(1).edgecase(rs)
else -> edge
}
}

@Deprecated("use edgeCase", ReplaceWith("edgeCase(rs)"))
fun <A> List<Arb<A>>.edgecase(rs: RandomSource): A? = edgeCase(rs)

/**
* Collects the edge cases from this arb.
* Will stop after the given number of iterations.
* This function is mainly used for testing.
*/
fun <A> Arb<A>.edgeCases(iterations: Int = 100, rs: RandomSource = RandomSource.default()): Set<A> =
generate(rs, EdgeConfig(edgeCasesGenerationProbability = 1.0))
fun <A> Arb<A>.edgecases(iterations: Int = 100, rs: RandomSource = RandomSource.default()): Set<A> =
generate(rs, EdgeConfig(edgecasesGenerationProbability = 1.0))
.take(iterations)
.map { it.value }
.toSet()

@Deprecated("use edgeCases", ReplaceWith("edgeCases(iterations, rs)"))
fun <A> Arb<A>.edgecases(iterations: Int = 100, rs: RandomSource = RandomSource.default()): Set<A> =
edgeCases(iterations, rs)

/**
* Returns a new [Arb] with the supplied edge cases replacing any existing edge cases.
*/
fun <A> Arb<A>.withEdgeCases(edgeCases: List<A>): Arb<A> = arbitrary(edgeCases) { this.next(it) }

@Deprecated("use withEdgeCases", ReplaceWith("withEdgeCases(edgecases)"))
fun <A> Arb<A>.withEdgecases(edgecases: List<A>): Arb<A> = withEdgeCases(edgecases)
fun <A> Arb<A>.withEdgecases(edgecases: List<A>): Arb<A> = arbitrary(edgecases) { this.next(it) }

/**
* Returns a new [Arb] with the supplied edge cases replacing any existing edge cases.
*/
fun <A> Arb<A>.withEdgeCases(vararg edgeCases: A): Arb<A> = withEdgeCases(edgeCases.toList())

@Deprecated("use withEdgeCases", ReplaceWith("withEdgeCases(edgecases)"))
fun <A> Arb<A>.withEdgecases(vararg edgecases: A): Arb<A> = withEdgeCases(edgecases.toList())
fun <A> Arb<A>.withEdgecases(vararg edgecases: A): Arb<A> = this.withEdgecases(edgecases.toList())

/**
* Returns a new [Arb] with the edge cases from this arb transformed by the given function [f].
*/
fun <A> Arb<A>.modifyEdgeCases(f: (A) -> A?): Arb<A> = object : Arb<A>() {
override fun edgeCase(rs: RandomSource): A? = this@modifyEdgeCases.edgeCase(rs)?.let(f)
override fun sample(rs: RandomSource): Sample<A> = this@modifyEdgeCases.sample(rs)
fun <A> Arb<A>.modifyEdgecases(f: (A) -> A?): Arb<A> = object : Arb<A>() {
override fun edgecase(rs: RandomSource): A? = this@modifyEdgecases.edgecase(rs)?.let(f)
override fun sample(rs: RandomSource): Sample<A> = this@modifyEdgecases.sample(rs)
}

@Deprecated("use modifyEdgeCases", ReplaceWith("modifyEdgeCases(f)"))
fun <A> Arb<A>.modifyEdgecases(f: (A) -> A?): Arb<A> = modifyEdgeCases(f)
Expand Up @@ -12,8 +12,8 @@ import io.kotest.property.Sample
*/
fun <A> Arb<A>.filter(predicate: (A) -> Boolean): Arb<A> = object : Arb<A>() {

override fun edgeCase(rs: RandomSource): A? =
generateSequence { this@filter.edgeCase(rs) }
override fun edgecase(rs: RandomSource): A? =
generateSequence { this@filter.edgecase(rs) }
.take(PropertyTesting.maxFilterAttempts)
.filter(predicate)
.firstOrNull()
Expand Down

0 comments on commit 0f46027

Please sign in to comment.