From 1c34d3a555ca36dfb02d3e214d8661a7cbd416bc 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: Thu, 2 May 2024 14:44:48 +0900 Subject: [PATCH] Modify the way a value class is used to output arbitrary value (#967) --- .../v1.0.x-kor/release-notes/_index.md | 6 +++++ docs/content/v1.0.x/release-notes/_index.md | 6 +++++ .../KotlinDurationIntrospector.kt | 17 ++++++++++--- ...PrimaryConstructorArbitraryIntrospector.kt | 24 +++---------------- .../kotlin/property/KPropertyProperty.kt | 5 ++-- .../fixturemonkey/kotlin/type/KotlinTypes.kt | 9 ++++++- ...aryConstructorArbitraryIntrospectorTest.kt | 23 ++++++++++++++---- ...nstructorArbitraryIntrospectorTestSpecs.kt | 5 ++++ .../tests/kotlin/ValueClassTest.kt | 13 ++++++++++ 9 files changed, 77 insertions(+), 31 deletions(-) diff --git a/docs/content/v1.0.x-kor/release-notes/_index.md b/docs/content/v1.0.x-kor/release-notes/_index.md index af31b3ca0..c97e20a28 100644 --- a/docs/content/v1.0.x-kor/release-notes/_index.md +++ b/docs/content/v1.0.x-kor/release-notes/_index.md @@ -6,6 +6,12 @@ docs: weight: 100 --- +sectionStart +### v.1.0.17 +Modify the way a value class is used to output arbitrary value + +sectionEnd + sectionStart ### v.1.0.16 Add resolve the candidate concrete type of container type. diff --git a/docs/content/v1.0.x/release-notes/_index.md b/docs/content/v1.0.x/release-notes/_index.md index ca2a51304..4a66fd22b 100644 --- a/docs/content/v1.0.x/release-notes/_index.md +++ b/docs/content/v1.0.x/release-notes/_index.md @@ -6,6 +6,12 @@ docs: weight: 100 --- +sectionStart +### v.1.0.17 +Modify the way a value class is used to output arbitrary value + +sectionEnd + sectionStart ### v.1.0.16 Add resolve the candidate concrete type of container type. 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 index 8a32a1176..903a0e990 100644 --- 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 @@ -29,8 +29,13 @@ import org.apiguardian.api.API import kotlin.reflect.full.primaryConstructor import kotlin.time.Duration import kotlin.time.DurationUnit +import kotlin.time.DurationUnit.NANOSECONDS import kotlin.time.toDuration +private const val STORAGE_UNIT = "storageUnit" +private const val IN_WHOLE_NANOSECONDS = "inWholeNanoseconds" +private const val IN_WHOLE_MILLISECONDS = "inWholeMilliseconds" + @API(since = "1.0.15", status = API.Status.EXPERIMENTAL) class KotlinDurationIntrospector : ArbitraryIntrospector, Matcher { override fun match(property: Property) = DURATION_TYPE_MATCHER.match(property) @@ -45,9 +50,15 @@ class KotlinDurationIntrospector : ArbitraryIntrospector, Matcher { 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()) + val arbitrariesByPropertyName = it.mapKeys { map -> map.key.objectProperty.property.name } + val durationUnit = arbitrariesByPropertyName[STORAGE_UNIT] as DurationUnit + + val value = when (durationUnit) { + NANOSECONDS -> arbitrariesByPropertyName[IN_WHOLE_NANOSECONDS] as Long + else -> arbitrariesByPropertyName[IN_WHOLE_MILLISECONDS] as Long + } + + value.toDuration(durationUnit) } ) } 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 890c647dc..96ac913b4 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 @@ -37,7 +37,6 @@ import kotlin.reflect.KFunction import kotlin.reflect.KParameter import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.isAccessible -import kotlin.reflect.jvm.jvmErasure @API(since = "0.4.0", status = MAINTAINED) class PrimaryConstructorArbitraryIntrospector : ArbitraryIntrospector { @@ -63,12 +62,13 @@ class PrimaryConstructorArbitraryIntrospector : ArbitraryIntrospector { .build { val arbitrariesByPropertyName: Map = it.mapKeys { map -> map.key.objectProperty.property.name } - val generatedByParameters = mutableMapOf() + for (parameter in constructor.parameters) { - val resolvedArbitrary = resolveArbitrary(parameter, arbitrariesByPropertyName) + val resolvedArbitrary = arbitrariesByPropertyName[parameter.name] generatedByParameters[parameter] = resolvedArbitrary } + constructor.callBy(generatedByParameters) }, ) @@ -76,24 +76,6 @@ class PrimaryConstructorArbitraryIntrospector : ArbitraryIntrospector { override fun getRequiredPropertyGenerator(property: Property): PropertyGenerator = KOTLIN_PROPERTY_GENERATOR - private fun resolveArbitrary( - parameter: KParameter, - arbitrariesByPropertyName: Map, - ): Any? { - return try { - val parameterKotlinType = parameter.type.jvmErasure - if (parameterKotlinType.isValue) { - parameterKotlinType.primaryConstructor!!.isAccessible = true - parameterKotlinType.primaryConstructor!!.call(arbitrariesByPropertyName[parameter.name]) - } else { - arbitrariesByPropertyName[parameter.name] - } - } catch (ex: Exception) { - // omitted - null - } - } - companion object { val INSTANCE = PrimaryConstructorArbitraryIntrospector() private val LOGGER = LoggerFactory.getLogger(PrimaryConstructorArbitraryIntrospector::class.java) diff --git a/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/property/KPropertyProperty.kt b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/property/KPropertyProperty.kt index 46ad60700..21afbd20a 100644 --- a/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/property/KPropertyProperty.kt +++ b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/property/KPropertyProperty.kt @@ -22,9 +22,9 @@ import com.navercorp.fixturemonkey.api.property.Property import org.apiguardian.api.API import org.slf4j.LoggerFactory import java.lang.reflect.AnnotatedType -import java.lang.reflect.Modifier import java.lang.reflect.Type import kotlin.reflect.KProperty +import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaMethod @@ -44,7 +44,8 @@ data class KPropertyProperty( override fun getValue(instance: Any): Any? { val javaMethod = this.kProperty.getter.javaMethod - if (javaMethod != null && Modifier.isPublic(javaMethod.modifiers)) { + if (javaMethod != null) { + this.kProperty.isAccessible = true return this.kProperty.getter.call(instance) } diff --git a/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/type/KotlinTypes.kt b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/type/KotlinTypes.kt index 3f52f5891..f25f392b5 100644 --- a/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/type/KotlinTypes.kt +++ b/fixture-monkey-kotlin/src/main/kotlin/com/navercorp/fixturemonkey/kotlin/type/KotlinTypes.kt @@ -26,6 +26,7 @@ import java.lang.reflect.AnnotatedType import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.lang.reflect.TypeVariable +import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KParameter import kotlin.reflect.KProperty @@ -35,7 +36,13 @@ import kotlin.reflect.jvm.javaType @API(since = "0.4.0", status = API.Status.INTERNAL) fun getAnnotatedType(ownerType: AnnotatedType, kProperty: KProperty<*>): AnnotatedType { - val type = kProperty.returnType.javaType + val kClassifier = kProperty.returnType.classifier + val type = if (kClassifier is KClass<*> && kClassifier.isValue) { + kClassifier.java + } else { + kProperty.returnType.javaType + } + val annotations = mutableSetOf() annotations.addAll(kProperty.findAnnotations()) annotations.addAll(kProperty.javaField?.annotations?.toList() ?: listOf()) 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 c877f8b5f..5edf1a625 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 @@ -23,12 +23,14 @@ 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 java.util.Random import kotlin.time.Duration +import kotlin.time.DurationUnit +import kotlin.time.toDuration class PrimaryConstructorArbitraryIntrospectorTest { private val sut: FixtureMonkey = FixtureMonkey.builder() @@ -76,13 +78,26 @@ class PrimaryConstructorArbitraryIntrospectorTest { } @Property - fun sampleDurationInContainer() { + fun sampleDurationValue() { + // when + val durationValue = sut.giveMeBuilder() + .sample() + + then(durationValue.duration).isNotNull() + } + + @Property + fun setDurationValue() { + // given + val duration = Random().nextLong().toDuration(DurationUnit.values().random()) + // when val one = sut.giveMeBuilder() - .set(DurationValue::duration, Arbitraries.longs().between(Long.MIN_VALUE, 0)) + .set(DurationValue::duration, duration) .sample() - then(one.duration).isNotEqualTo(Duration.INFINITE) + // then + then(one.duration).isEqualTo(duration) } @Property 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 cd65c750f..5ab034c2c 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 @@ -65,3 +65,8 @@ interface InterfaceClass { class DurationValue( val duration: Duration = Duration.INFINITE, ) + +@JvmInline +value class JvmInlineValue( + val intValue: Int, +) diff --git a/fixture-monkey-tests/kotlin-tests/src/test/kotlin/com/navercorp/fixturemonkey/tests/kotlin/ValueClassTest.kt b/fixture-monkey-tests/kotlin-tests/src/test/kotlin/com/navercorp/fixturemonkey/tests/kotlin/ValueClassTest.kt index b2044bf45..0a562ef87 100644 --- a/fixture-monkey-tests/kotlin-tests/src/test/kotlin/com/navercorp/fixturemonkey/tests/kotlin/ValueClassTest.kt +++ b/fixture-monkey-tests/kotlin-tests/src/test/kotlin/com/navercorp/fixturemonkey/tests/kotlin/ValueClassTest.kt @@ -22,6 +22,7 @@ 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 org.assertj.core.api.BDDAssertions.then import org.junit.jupiter.api.Test @@ -44,6 +45,18 @@ class ValueClassTest { then(actual.foo).isNotNull } + @Test + fun setValueClassProperty() { + class ValueClassObject(val foo: Foo) + val settingFoo = Foo("hello") + + val actual: ValueClassObject = SUT.giveMeBuilder() + .set(ValueClassObject::foo, settingFoo) + .sample() + + then(actual.foo).isEqualTo(settingFoo) + } + @Test fun valueClassPropertyFixed() { class ValueClassObject(val foo: Foo)