From faee18c4f6147c442adfb27f4fff95bfbf96cb11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=EC=B0=BD?= <92219795+this-is-spear@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:13:04 +0900 Subject: [PATCH] Support generating kotlin.time.Duration (#929) --- .../fixturemonkey/kotlin/KotlinPlugin.kt | 6 +++ .../KotlinDurationIntrospector.kt | 54 +++++++++++++++++++ ...PrimaryConstructorArbitraryIntrospector.kt | 6 +-- .../fixturemonkey/kotlin/matcher/Matchers.kt | 3 ++ ...aryConstructorArbitraryIntrospectorTest.kt | 33 ++++++++++++ ...nstructorArbitraryIntrospectorTestSpecs.kt | 6 +++ 6 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/introspector/KotlinDurationIntrospector.kt diff --git a/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/KotlinPlugin.kt b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/KotlinPlugin.kt index 72870acf1..f770e1f31 100644 --- a/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/KotlinPlugin.kt +++ b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/KotlinPlugin.kt @@ -30,9 +30,11 @@ import com.navercorp.fixturemonkey.kotlin.generator.PairDecomposedContainerValue import com.navercorp.fixturemonkey.kotlin.generator.TripleContainerPropertyGenerator import com.navercorp.fixturemonkey.kotlin.generator.TripleDecomposedContainerValueFactory import com.navercorp.fixturemonkey.kotlin.instantiator.KotlinInstantiatorProcessor +import com.navercorp.fixturemonkey.kotlin.introspector.KotlinDurationIntrospector import com.navercorp.fixturemonkey.kotlin.introspector.PairIntrospector import com.navercorp.fixturemonkey.kotlin.introspector.PrimaryConstructorArbitraryIntrospector import com.navercorp.fixturemonkey.kotlin.introspector.TripleIntrospector +import com.navercorp.fixturemonkey.kotlin.matcher.Matchers.DURATION_TYPE_MATCHER import com.navercorp.fixturemonkey.kotlin.matcher.Matchers.PAIR_TYPE_MATCHER import com.navercorp.fixturemonkey.kotlin.matcher.Matchers.TRIPLE_TYPE_MATCHER import com.navercorp.fixturemonkey.kotlin.property.KotlinPropertyGenerator @@ -66,6 +68,10 @@ class KotlinPlugin : Plugin { InterfaceKFunctionPropertyGenerator(), ), ) + .insertFirstArbitraryIntrospector( + DURATION_TYPE_MATCHER, + KotlinDurationIntrospector(), + ) .insertFirstArbitraryContainerPropertyGenerator( PAIR_TYPE_MATCHER, PairContainerPropertyGenerator(), diff --git a/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/introspector/KotlinDurationIntrospector.kt b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/introspector/KotlinDurationIntrospector.kt new file mode 100644 index 000000000..8a32a1176 --- /dev/null +++ b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/introspector/KotlinDurationIntrospector.kt @@ -0,0 +1,54 @@ +/* + * Fixture Monkey + * + * Copyright (c) 2021-present NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.fixturemonkey.kotlin.introspector + +import com.navercorp.fixturemonkey.api.arbitrary.CombinableArbitrary +import com.navercorp.fixturemonkey.api.generator.ArbitraryGeneratorContext +import com.navercorp.fixturemonkey.api.introspector.ArbitraryIntrospector +import com.navercorp.fixturemonkey.api.introspector.ArbitraryIntrospectorResult +import com.navercorp.fixturemonkey.api.matcher.Matcher +import com.navercorp.fixturemonkey.api.property.Property +import com.navercorp.fixturemonkey.kotlin.matcher.Matchers.DURATION_TYPE_MATCHER +import org.apiguardian.api.API +import kotlin.reflect.full.primaryConstructor +import kotlin.time.Duration +import kotlin.time.DurationUnit +import kotlin.time.toDuration + +@API(since = "1.0.15", status = API.Status.EXPERIMENTAL) +class KotlinDurationIntrospector : ArbitraryIntrospector, Matcher { + override fun match(property: Property) = DURATION_TYPE_MATCHER.match(property) + + override fun introspect(context: ArbitraryGeneratorContext): ArbitraryIntrospectorResult { + val kClass = Duration::class + val primaryConstructor = kClass.primaryConstructor!! + + require(primaryConstructor.parameters.size == 1) { "Duration class must have only one parameter" } + + return ArbitraryIntrospectorResult( + CombinableArbitrary.objectBuilder() + .properties(context.combinableArbitrariesByArbitraryProperty) + .build { + val parameterName = primaryConstructor.parameters[0].name + val parameterValue = it.mapKeys { map -> map.key.objectProperty.property.name }[parameterName] as Long + parameterValue.toDuration(DurationUnit.values().random()) + } + ) + } +} diff --git a/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/introspector/PrimaryConstructorArbitraryIntrospector.kt b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/introspector/PrimaryConstructorArbitraryIntrospector.kt index 97fa7dda7..890c647dc 100644 --- a/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/introspector/PrimaryConstructorArbitraryIntrospector.kt +++ b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/introspector/PrimaryConstructorArbitraryIntrospector.kt @@ -80,9 +80,9 @@ class PrimaryConstructorArbitraryIntrospector : ArbitraryIntrospector { parameter: KParameter, arbitrariesByPropertyName: Map, ): Any? { - try { + return try { val parameterKotlinType = parameter.type.jvmErasure - return if (parameterKotlinType.isValue) { + if (parameterKotlinType.isValue) { parameterKotlinType.primaryConstructor!!.isAccessible = true parameterKotlinType.primaryConstructor!!.call(arbitrariesByPropertyName[parameter.name]) } else { @@ -90,7 +90,7 @@ class PrimaryConstructorArbitraryIntrospector : ArbitraryIntrospector { } } catch (ex: Exception) { // omitted - return arbitrariesByPropertyName[parameter.name] + null } } diff --git a/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/matcher/Matchers.kt b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/matcher/Matchers.kt index 6bd23e845..4681d009e 100644 --- a/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/matcher/Matchers.kt +++ b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/matcher/Matchers.kt @@ -3,9 +3,12 @@ package com.navercorp.fixturemonkey.kotlin.matcher import com.navercorp.fixturemonkey.api.matcher.AssignableTypeMatcher import com.navercorp.fixturemonkey.api.matcher.DoubleGenericTypeMatcher import com.navercorp.fixturemonkey.api.matcher.TripleGenericTypeMatcher +import kotlin.time.Duration object Matchers { val PAIR_TYPE_MATCHER = AssignableTypeMatcher(Pair::class.java).intersect(DoubleGenericTypeMatcher()) val TRIPLE_TYPE_MATCHER = AssignableTypeMatcher(Triple::class.java).intersect(TripleGenericTypeMatcher()) + + val DURATION_TYPE_MATCHER = AssignableTypeMatcher(Duration::class.java) } diff --git a/fixture-monkey-kotlin/src/test/kotlin/com/navercorp/fixturemonkey/kotlin/test/PrimaryConstructorArbitraryIntrospectorTest.kt b/fixture-monkey-kotlin/src/test/kotlin/com/navercorp/fixturemonkey/kotlin/test/PrimaryConstructorArbitraryIntrospectorTest.kt index 6419ca026..c877f8b5f 100644 --- a/fixture-monkey-kotlin/src/test/kotlin/com/navercorp/fixturemonkey/kotlin/test/PrimaryConstructorArbitraryIntrospectorTest.kt +++ b/fixture-monkey-kotlin/src/test/kotlin/com/navercorp/fixturemonkey/kotlin/test/PrimaryConstructorArbitraryIntrospectorTest.kt @@ -20,10 +20,15 @@ package com.navercorp.fixturemonkey.kotlin.test import com.navercorp.fixturemonkey.FixtureMonkey import com.navercorp.fixturemonkey.kotlin.KotlinPlugin +import com.navercorp.fixturemonkey.kotlin.giveMeBuilder import com.navercorp.fixturemonkey.kotlin.giveMeOne +import com.navercorp.fixturemonkey.kotlin.set +import net.jqwik.api.Arbitraries import net.jqwik.api.Property import org.assertj.core.api.BDDAssertions.then import org.assertj.core.api.BDDAssertions.thenNoException +import org.junit.jupiter.api.assertAll +import kotlin.time.Duration class PrimaryConstructorArbitraryIntrospectorTest { private val sut: FixtureMonkey = FixtureMonkey.builder() @@ -63,6 +68,34 @@ class PrimaryConstructorArbitraryIntrospectorTest { then(actual).isNotEqualTo("default_value") } + @Property + fun sampleDuration() { + // when + val duration = sut.giveMeOne() + then(duration).isNotNull() + } + + @Property + fun sampleDurationInContainer() { + // when + val one = sut.giveMeBuilder() + .set(DurationValue::duration, Arbitraries.longs().between(Long.MIN_VALUE, 0)) + .sample() + + then(one.duration).isNotEqualTo(Duration.INFINITE) + } + + @Property + fun sampleGenericDuration() { + // when + val one = sut.giveMeOne>() + + assertAll( + { one.forEach { then(it).isNotNull() } }, + { then(one.size).isGreaterThanOrEqualTo(0) } + ) + } + @Property fun sampleSecondaryConstructor() { // when diff --git a/fixture-monkey-kotlin/src/test/kotlin/com/navercorp/fixturemonkey/kotlin/test/PrimaryConstructorArbitraryIntrospectorTestSpecs.kt b/fixture-monkey-kotlin/src/test/kotlin/com/navercorp/fixturemonkey/kotlin/test/PrimaryConstructorArbitraryIntrospectorTestSpecs.kt index 11ab9f050..cd65c750f 100644 --- a/fixture-monkey-kotlin/src/test/kotlin/com/navercorp/fixturemonkey/kotlin/test/PrimaryConstructorArbitraryIntrospectorTestSpecs.kt +++ b/fixture-monkey-kotlin/src/test/kotlin/com/navercorp/fixturemonkey/kotlin/test/PrimaryConstructorArbitraryIntrospectorTestSpecs.kt @@ -18,6 +18,8 @@ package com.navercorp.fixturemonkey.kotlin.test +import kotlin.time.Duration + class PrimaryConstructor( val intValue: Int, val stringValue: String, @@ -59,3 +61,7 @@ class SecondaryConstructor( interface InterfaceClass { fun test() } + +class DurationValue( + val duration: Duration = Duration.INFINITE, +)