From d3a15a80fe938db0717181f3d230f69b74cee613 Mon Sep 17 00:00:00 2001 From: Sergio Pro <22973227+serpro69@users.noreply.github.com> Date: Sat, 10 Feb 2024 15:55:58 +0100 Subject: [PATCH] Fix constructor-less types in collections when generating randomClassInstance Fix #204 --- CHANGELOG.adoc | 3 +- .../provider/misc/RandomClassProvider.kt | 9 +++ .../provider/misc/RandomClassProviderTest.kt | 56 ++++++++++++++++++- docs/src/orchid/resources/wiki/extras.md | 1 + 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index b69cd8606..f490881c5 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -4,6 +4,7 @@ [discrete] === Added +* https://github.com/serpro69/kotlin-faker/issues/208[#208] [core] Allow `StringProvider#regexify` to take Regex as input * https://github.com/serpro69/kotlin-faker/pull/202[#202] [core] Allow `randomClassInstance` to directly use predefined generators [discrete] @@ -14,7 +15,7 @@ [discrete] === Fixed -* ... +* https://github.com/serpro69/kotlin-faker/issues/204[#204] [core] Fix RandomClassProvider handling "constructor-less" types in collections [discrete] === Other diff --git a/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt index f62981d11..4037ee41f 100644 --- a/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt +++ b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt @@ -108,6 +108,9 @@ class RandomClassProvider { @JvmSynthetic @PublishedApi internal fun KClass.randomClassInstance(config: RandomProviderConfig): T { + // https://github.com/serpro69/kotlin-faker/issues/212 + if (this.isInner) throw UnsupportedOperationException("Inner classes are not yet supported") + val defaultInstance: T? by lazy { if (config.constructorParamSize == -1 && config.constructorFilterStrategy == NO_ARGS) { randomPrimitiveOrNull() as T? ?: try { @@ -127,6 +130,12 @@ class RandomClassProvider { ) as T? } + // Handle cases where "constructor-less" type is not a direct parameter of the generated class, + // but is a collection type, for example + // https://github.com/serpro69/kotlin-faker/issues/204 + if (this.java.isEnum) return randomEnumOrNull() as T + if (this.java.isPrimitive) return randomPrimitiveOrNull() as T + return objectInstance ?: defaultInstance ?: run { val constructors = constructors.filter { it.visibility == KVisibility.PUBLIC } diff --git a/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt b/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt index c264d625b..3e1980080 100644 --- a/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt +++ b/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt @@ -6,12 +6,14 @@ import io.kotest.assertions.throwables.shouldNotThrow import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.ints.shouldBeInRange import io.kotest.matchers.maps.shouldHaveSize import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldHaveLength import io.kotest.matchers.types.instanceOf import io.kotest.matchers.types.shouldNotBeSameInstanceAs +import org.junit.jupiter.api.assertThrows import java.util.* import kotlin.reflect.full.declaredMemberProperties @@ -394,7 +396,15 @@ class RandomClassProviderTest : DescribeSpec({ class TestClass( val list: List, val set: Set, - val map: Map + val map: Map, + // "primitives" (see https://github.com/serpro69/kotlin-faker/issues/204 ) + val charList: List, + val intSet: Set, + val boolMap: Map, + // enums (see https://github.com/serpro69/kotlin-faker/issues/204 ) + val enumList: List, + val enumSet: Set, + val enumMap: Map, ) it("should generate Collections with default size 1") { @@ -402,6 +412,12 @@ class RandomClassProviderTest : DescribeSpec({ testClass.list shouldHaveSize 1 testClass.set shouldHaveSize 1 testClass.map shouldHaveSize 1 + testClass.charList shouldHaveSize 1 + testClass.intSet shouldHaveSize 1 + testClass.boolMap shouldHaveSize 1 + testClass.enumList shouldHaveSize 1 + testClass.enumSet shouldHaveSize 1 + testClass.enumMap shouldHaveSize 1 } it("should generate Collections with pre-configured size") { @@ -411,14 +427,31 @@ class RandomClassProviderTest : DescribeSpec({ testClass.list shouldHaveSize 10 testClass.set shouldHaveSize 10 testClass.map shouldHaveSize 10 + testClass.charList shouldHaveSize 10 + testClass.intSet shouldHaveSize 10 + // boolean-key based map can only have 1 or 2 entries, depending on the randomness of the generated key + testClass.boolMap.size shouldBeInRange 1..2 + testClass.enumList shouldHaveSize 10 + // we only have 3 enum classes, so a set can't have more values total than that + testClass.enumSet.size shouldBeInRange 1..3 + // enum-key based map can only have up to 3 entries in this case, depending on the randomness of the generated key + testClass.enumMap.size shouldBeInRange 1..3 } it("should generate Collections with pre-configured type generation") { val testClass = randomProvider.randomClassInstance { typeGenerator> { listOf() } + typeGenerator> { listOf() } + typeGenerator> { listOf() } } testClass.list shouldHaveSize 0 testClass.set shouldHaveSize 1 testClass.map shouldHaveSize 1 + testClass.charList shouldHaveSize 0 + testClass.intSet shouldHaveSize 1 + testClass.boolMap shouldHaveSize 1 + testClass.enumList shouldHaveSize 0 + testClass.enumSet shouldHaveSize 1 + testClass.enumMap shouldHaveSize 1 } } @@ -464,6 +497,16 @@ class RandomClassProviderTest : DescribeSpec({ } } + describe("an inner class") { + it("should throw exception when generated directly") { + assertThrows { randomProvider.randomClassInstance() } + } + it("should throw exception when included as constructor parameter") { + class Test(val goBuild: Go.BuildNum) + assertThrows { randomProvider.randomClassInstance() } + } + } + describe("RandomClassProvider configuration") { class Foo(val int: Int) class Bar(val foo: Foo) @@ -724,12 +767,19 @@ enum class TestEnum { @Suppress("CanSealedSubClassBeObject", "unused") sealed class TestSealedCls { - object Kotlin : TestSealedCls() + data object Kotlin : TestSealedCls() class Java : TestSealedCls() } @Suppress("unused") -class Go(val name: String) : TestSealedCls() +class Go(val name: String) : TestSealedCls() { + + class Version(val ver: String) + + inner class BuildNum(v: Version, i: Int) { + val innerVersion = "${v.ver}+$i" + } +} object TestObject diff --git a/docs/src/orchid/resources/wiki/extras.md b/docs/src/orchid/resources/wiki/extras.md index 264c50ba4..fdfd9b22f 100644 --- a/docs/src/orchid/resources/wiki/extras.md +++ b/docs/src/orchid/resources/wiki/extras.md @@ -29,6 +29,7 @@ There are some rules to keep in mind: - By default, the constructor with the least number of arguments is used (This can be configured - read on.) - `kolin.Array` type in the constructor is not supported at the moment +- Inner classes (either direct generation or as class parameter type) are not supported at the moment Random instance generation is available through `Faker().randomProvider`: