diff --git a/kotest-property/src/commonMain/kotlin/io/kotest/property/arbitrary/array.kt b/kotest-property/src/commonMain/kotlin/io/kotest/property/arbitrary/array.kt index f7d97ae735f..5a1b15d2253 100644 --- a/kotest-property/src/commonMain/kotlin/io/kotest/property/arbitrary/array.kt +++ b/kotest-property/src/commonMain/kotlin/io/kotest/property/arbitrary/array.kt @@ -12,3 +12,15 @@ internal inline fun Arb.Companion.toPrimitiveArray( is Arb -> generateArrayLength is Exhaustive -> generateArrayLength.toArb() }.flatMap { length -> Arb.list(generateContents, length..length) }.map { it.toArray() } + +inline fun Arb.Companion.array( + gen: Gen, + range: IntRange = 0..100, + crossinline toArray: Collection.() -> Array = { this.toTypedArray() } +): Arb> { + check(!range.isEmpty()) + check(range.first >= 0) + return arbitrary { + list(gen, range).bind().toArray() + } +} diff --git a/kotest-property/src/jvmMain/kotlin/io/kotest/property/arbitrary/targetDefaultForClassName.kt b/kotest-property/src/jvmMain/kotlin/io/kotest/property/arbitrary/targetDefaultForClassName.kt index 718a946da6c..0b30a1a0dbe 100644 --- a/kotest-property/src/jvmMain/kotlin/io/kotest/property/arbitrary/targetDefaultForClassName.kt +++ b/kotest-property/src/jvmMain/kotlin/io/kotest/property/arbitrary/targetDefaultForClassName.kt @@ -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 @@ -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") @@ -45,6 +48,17 @@ fun targetDefaultForType(providedArbs: Map, 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 + 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<*>) diff --git a/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ArrayTest.kt b/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ArrayTest.kt index 09c0e187523..8462a273c94 100644 --- a/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ArrayTest.kt +++ b/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ArrayTest.kt @@ -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>(PropTestConfig(iterations = 10_000)) { + it.shouldHaveAtMostSize(100) + } + + forAll>(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) + } } } }) diff --git a/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ReflectiveBindTest.kt b/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ReflectiveBindTest.kt index 2bb0503bf2c..dea51c5a90c 100644 --- a/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ReflectiveBindTest.kt +++ b/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ReflectiveBindTest.kt @@ -159,6 +159,12 @@ class ReflectiveBindTest : StringSpec( val listArb = Arb.bind>() listArb.next().shouldBeInstanceOf>() + val arrayArb = Arb.bind>() + arrayArb.next().shouldBeInstanceOf>() + + val arrayArbWithClass = Arb.bind>() + arrayArbWithClass.next().shouldBeInstanceOf>() + val bigDecimalArb = Arb.bind() bigDecimalArb.next().shouldBeInstanceOf() }