Skip to content

Commit

Permalink
Added support for array arbitraries (#3728)
Browse files Browse the repository at this point in the history
Co-authored-by: Sam <sam@sksamuel.com>
Co-authored-by: Leonardo Colman Lopes <dev@leonardo.colman.com.br>
  • Loading branch information
3 people committed Oct 25, 2023
1 parent d950fdb commit ba28879
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 10 deletions.
Expand Up @@ -12,3 +12,15 @@ internal inline fun <P, A> Arb.Companion.toPrimitiveArray(
is Arb -> generateArrayLength
is Exhaustive -> generateArrayLength.toArb()
}.flatMap { length -> Arb.list(generateContents, length..length) }.map { it.toArray() }

inline fun <reified A> Arb.Companion.array(
gen: Gen<A>,
range: IntRange = 0..100,
crossinline toArray: Collection<A>.() -> Array<A> = { this.toTypedArray() }
): Arb<Array<A>> {
check(!range.isEmpty())
check(range.first >= 0)
return arbitrary {
list(gen, range).bind().toArray()
}
}
@@ -1,5 +1,6 @@
package io.kotest.property.arbitrary

import io.kotest.mpp.bestName
import io.kotest.property.Arb
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
Expand All @@ -17,6 +18,8 @@ import java.util.Date
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.javaType
import kotlin.reflect.jvm.jvmErasure
import kotlin.reflect.typeOf

@Suppress("UNCHECKED_CAST")
Expand Down Expand Up @@ -45,6 +48,17 @@ fun targetDefaultForType(providedArbs: Map<KClass<*>, Arb<*>> = emptyMap(), type
val upperBound = type.arguments.first().type ?: error("No bound for List")
Arb.list(Arb.forType(providedArbs, upperBound) as Arb<*>)
}
clazz.java.isArray -> {
val upperBound = type.arguments.first().type ?: error("No bound for Array")
Arb.array(Arb.forType(providedArbs, upperBound) as Arb<*>) {
val upperBoundKClass = (upperBound.classifier as? KClass<*>) ?: error("No classifier for $upperBound")
val array = java.lang.reflect.Array.newInstance(upperBoundKClass.javaObjectType, this.size) as Array<Any?>
for ((i, item) in this.withIndex()) {
java.lang.reflect.Array.set(array, i, item)
}
array
}
}
clazz.isSubclassOf(Set::class) -> {
val upperBound = type.arguments.first().type ?: error("No bound for Set")
val upperBoundKClass = (upperBound.classifier as? KClass<*>)
Expand Down
@@ -1,24 +1,95 @@
package com.sksamuel.kotest.property.arbitrary

import io.kotest.core.spec.style.FunSpec
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.inspectors.forAll
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.collections.shouldHaveAtLeastSize
import io.kotest.matchers.collections.shouldHaveAtMostSize
import io.kotest.matchers.collections.shouldNotBeEmpty
import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual
import io.kotest.matchers.ints.shouldBeLessThanOrEqual
import io.kotest.matchers.shouldBe
import io.kotest.property.Arb
import io.kotest.property.arbitrary.*
import io.kotest.property.Exhaustive
import io.kotest.property.PropTestConfig
import io.kotest.property.arbitrary.array
import io.kotest.property.arbitrary.byte
import io.kotest.property.arbitrary.byteArray
import io.kotest.property.arbitrary.constant
import io.kotest.property.arbitrary.double
import io.kotest.property.arbitrary.edgecases
import io.kotest.property.arbitrary.int
import io.kotest.property.arbitrary.positiveInt
import io.kotest.property.arbitrary.take
import io.kotest.property.checkAll
import io.kotest.property.exhaustive.constant
import io.kotest.property.forAll

class ArrayTest : FunSpec({
test("ByteArray should generate specified lengths") {
Arb.byteArray(Arb.int(5, 15), Arb.byte()).take(1000).toList().forAll {
it.size.shouldBeGreaterThanOrEqual(5)
it.size.shouldBeLessThanOrEqual(15)
class ArrayTest : DescribeSpec({
describe("ByteArray should") {
it("generate specified lengths") {
Arb.byteArray(Arb.int(5, 15), Arb.byte()).take(1000).toList().forAll {
it.size.shouldBeGreaterThanOrEqual(5)
it.size.shouldBeLessThanOrEqual(15)
}
}

it("populate random byte values") {
Arb.byteArray(Arb.constant(1000000), Arb.byte()).take(10).toList().forAll {
it.toSet().size shouldBe 256
}
}
}

test("ByteArray should populate random byte values") {
Arb.byteArray(Arb.constant(1000000), Arb.byte()).take(10).toList().forAll {
it.toSet().size shouldBe 256
describe("Arb.array should") {
it("not include empty edge cases as first sample") {
val numGen = Arb.array(Arb.int(), 1..10)
forAll(1, numGen) { it.isNotEmpty() }
}

it("return arrays of underlying generators") {
val gen = Arb.array(Exhaustive.constant(1), 2..10)
checkAll(gen) {
it.shouldHaveAtLeastSize(2)
it.shouldHaveAtMostSize(10)
it.toSet() shouldBe setOf(1)
}
}

it("include repeated elements in edge cases") {
val edgeCase = Arb.positiveInt().edgecases().firstOrNull()
Arb.array(Arb.positiveInt()).edgecases() shouldContain listOf(edgeCase, edgeCase)
Arb.array(Arb.positiveInt(), 4..6).edgecases() shouldContain listOf(edgeCase, edgeCase, edgeCase, edgeCase)
}

it("include empty array in edge cases") {
Arb.array(Arb.positiveInt()).edgecases() shouldContain emptyArray()
}

it("respect bounds in edge cases") {
val edges = Arb.array(Arb.positiveInt(), 2..10).edgecases().toSet()
edges.forAll { it.shouldNotBeEmpty() }
}

it("generate arrays of length up to 100 by default") {
checkAll(10_000, Arb.array(Arb.double())) {
it.shouldHaveAtMostSize(100)
}

checkAll<Array<Double>>(PropTestConfig(iterations = 10_000)) {
it.shouldHaveAtMostSize(100)
}

forAll<Array<Double>>(PropTestConfig(iterations = 10_000)) {
it.size <= 100
}
}

it("generate arrays in the given range") {
checkAll(1000, Arb.array(Arb.double(), 250..500)) {
it.shouldHaveAtLeastSize(250)
it.shouldHaveAtMostSize(500)
}
}
}
})
Expand Up @@ -159,6 +159,12 @@ class ReflectiveBindTest : StringSpec(
val listArb = Arb.bind<List<Int>>()
listArb.next().shouldBeInstanceOf<List<Int>>()

val arrayArb = Arb.bind<Array<Int>>()
arrayArb.next().shouldBeInstanceOf<Array<Int>>()

val arrayArbWithClass = Arb.bind<Array<Wobble>>()
arrayArbWithClass.next().shouldBeInstanceOf<Array<Wobble>>()

val bigDecimalArb = Arb.bind<BigDecimal>()
bigDecimalArb.next().shouldBeInstanceOf<BigDecimal>()
}
Expand Down

0 comments on commit ba28879

Please sign in to comment.