From 4c936b18ed73a12fe44ca56b7bdc26e53dfc7a0f Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 20 Jul 2022 21:42:15 +0200 Subject: [PATCH 01/36] #152 add failing tests for value classes --- .../test/kotlin/io/mockk/it/ValueClassTest.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt index 6acf64f9f..66fa6bec6 100644 --- a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt +++ b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt @@ -1,6 +1,7 @@ package io.mockk.it import io.mockk.* +import kotlin.jvm.JvmInline import kotlin.test.Test import kotlin.test.assertEquals @@ -43,6 +44,43 @@ class ValueClassTest { verify { mock.processValue(DummyValue(1)) } } + + @Test + fun `any matcher for value class`() { + val mock = mockk(relaxed = true) + val givenResult = 1 + every { mock.doSomething(any()) } returns givenResult + + val result = mock.doSomething(ValueDummy("moin")) + + assertEquals(givenResult, result) + } + + @Test + fun `slot for value class`() { + val mock = mockk(relaxed = true) + val slot = slot() + val givenResult = 1 + every { mock.doSomething(capture(slot)) } returns givenResult + + val givenParameter = ValueDummy("s") + + val result = mock.doSomething(givenParameter) + + assertEquals(givenResult, result) + assertEquals(givenParameter, slot.captured) + } + + @Test + fun `value class as return value`() { + val mock = mockk(relaxed = true) + val givenResult = ValueDummy("moin") + every { mock.getSomething() } returns givenResult + + val result = mock.getSomething() + + assertEquals(givenResult, result) + } } // TODO should be value class in kotlin 1.5+ @@ -54,3 +92,11 @@ private class DummyService { fun processValue(value: DummyValue) = DummyValue(0) } + +@JvmInline +value class ValueDummy(val value: String) + +interface ValueServiceDummy { + fun doSomething(value: ValueDummy): Int + fun getSomething(): ValueDummy +} From ada19d57d42aac1e69007cfbef14f8c5294e0ef8 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 20 Jul 2022 21:58:14 +0200 Subject: [PATCH 02/36] bump kotlin 1.7.10, and language level to 1.7 --- agent/jvm/api/mockk-agent-jvm.api | 8 ++++---- build.gradle | 7 +++---- gradle.properties | 2 +- .../mockk/impl/recording/states/VerifyingState.kt | 14 +++++++++----- .../src/main/kotlin/io/mockk/dependencies/Deps.kt | 2 +- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/agent/jvm/api/mockk-agent-jvm.api b/agent/jvm/api/mockk-agent-jvm.api index b4adb9e50..01c9d9979 100644 --- a/agent/jvm/api/mockk-agent-jvm.api +++ b/agent/jvm/api/mockk-agent-jvm.api @@ -73,7 +73,7 @@ public final class io/mockk/proxy/jvm/advice/jvm/SynchronizedMockHandlersMap : i public final fun containsValue (Ljava/lang/Object;)Z public final fun entrySet ()Ljava/util/Set; public fun get (Ljava/lang/Object;)Lio/mockk/proxy/MockKInvocationHandler; - public final fun get (Ljava/lang/Object;)Ljava/lang/Object; + public synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; public fun getEntries ()Ljava/util/Set; public fun getKeys ()Ljava/util/Set; public fun getSize ()I @@ -85,7 +85,7 @@ public final class io/mockk/proxy/jvm/advice/jvm/SynchronizedMockHandlersMap : i public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public fun putAll (Ljava/util/Map;)V public fun remove (Ljava/lang/Object;)Lio/mockk/proxy/MockKInvocationHandler; - public final fun remove (Ljava/lang/Object;)Ljava/lang/Object; + public synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object; public final fun size ()I public final fun values ()Ljava/util/Collection; } @@ -99,7 +99,7 @@ public final class io/mockk/proxy/jvm/advice/jvm/WeakMockHandlersMap : io/mockk/ public final fun containsValue (Ljava/lang/Object;)Z public final fun entrySet ()Ljava/util/Set; public fun get (Ljava/lang/Object;)Lio/mockk/proxy/MockKInvocationHandler; - public final fun get (Ljava/lang/Object;)Ljava/lang/Object; + public synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; public fun getEntries ()Ljava/util/Set; public fun getKeys ()Ljava/util/Set; public fun getSize ()I @@ -111,7 +111,7 @@ public final class io/mockk/proxy/jvm/advice/jvm/WeakMockHandlersMap : io/mockk/ public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public fun putAll (Ljava/util/Map;)V public fun remove (Ljava/lang/Object;)Lio/mockk/proxy/MockKInvocationHandler; - public final fun remove (Ljava/lang/Object;)Ljava/lang/Object; + public synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object; public final fun size ()I public final fun values ()Ljava/util/Collection; } diff --git a/build.gradle b/build.gradle index 5748153cd..acadb5bee 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ plugins { subprojects { subProject -> group = 'io.mockk' - ext.kotlin_version = findProperty('kotlin.version')?.toString() ?: '1.6.0' + ext.kotlin_version = findProperty('kotlin.version')?.toString() ?: '1.7.10' ext.kotlin_gradle_version = findProperty('kotlin.version')?.toString() ?: '1.6.0' repositories { @@ -40,9 +40,8 @@ subprojects { subProject -> tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { jvmTarget = "1.8" - apiVersion = "1.4" - languageVersion = "1.4" - useIR = findProperty("kotlin.ir.enabled")?.toBoolean() == true + apiVersion = "1.7" + languageVersion = "1.7" } } } diff --git a/gradle.properties b/gradle.properties index 5e0eb4319..22747070c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,4 @@ org.gradle.configureondemand=false org.gradle.jvmargs=-XX:MaxMetaspaceSize=768m # localrepo=build/mockk-repo localrepo=/Users/raibaz/.m2/repository -# kotlin.version=1.5.10 +kotlin.version=1.7.10 diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt index a3656bc90..c1eeeb747 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt @@ -60,10 +60,12 @@ class VerifyingState( val callsReport = VerificationHelpers.formatCalls(outcome.verifiedCalls) throw AssertionError("Inverse verification failed.\n\nVerified calls:\n$callsReport") } + is VerificationResult.Failure -> {} } } else { when (outcome) { is VerificationResult.Failure -> throw AssertionError("Verification failed: ${outcome.message}") + is VerificationResult.OK -> {} } } } @@ -86,17 +88,19 @@ class VerifyingState( } } - if (!calledStubs.isEmpty()) { + if (calledStubs.isNotEmpty()) { if (calledStubs.size == 1) { val calledStub = calledStubs[0] throw AssertionError(recorder.safeExec { "Verification failed: ${calledStub.toStr()} should not be called:\n" + - calledStub.allRecordedCalls().joinToString("\n") + calledStub.allRecordedCalls().joinToString("\n") }) } else { throw AssertionError(recorder.safeExec { - "Verification failed: ${calledStubs.map { it.toStr() }.joinToString(", ")} should not be called:\n" + - calledStubs.flatMap { it.allRecordedCalls() }.joinToString("\n") + "Verification failed: ${ + calledStubs.map { it.toStr() }.joinToString(", ") + } should not be called:\n" + + calledStubs.flatMap { it.allRecordedCalls() }.joinToString("\n") }) } } @@ -105,4 +109,4 @@ class VerifyingState( companion object { val log = Logger() } -} \ No newline at end of file +} diff --git a/plugins/dependencies/src/main/kotlin/io/mockk/dependencies/Deps.kt b/plugins/dependencies/src/main/kotlin/io/mockk/dependencies/Deps.kt index f1ef78c42..49f06d83d 100644 --- a/plugins/dependencies/src/main/kotlin/io/mockk/dependencies/Deps.kt +++ b/plugins/dependencies/src/main/kotlin/io/mockk/dependencies/Deps.kt @@ -8,7 +8,7 @@ object Deps { object Versions { const val androidTools = "4.1.1" const val dokka = "1.6.0" - const val kotlinDefault = "1.6.0" + const val kotlinDefault = "1.7.10" const val coroutines = "1.3.3" const val slfj = "1.7.32" const val logback = "1.2.10" From 7357c69252ded68de3e74801539c56ab5195fc9e Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 20 Jul 2022 21:59:42 +0200 Subject: [PATCH 03/36] http -> https in docs URLs --- README.md | 4 ++-- gradle/upload.gradle | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e128a735f..e9887768f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![mockk](doc/logo-site.png) ![kotlin](doc/kotlin-logo.png) [![Gitter](https://badges.gitter.im/mockk-io/Lobby.svg)](https://gitter.im/mockk-io/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) -[![Relase Version](https://img.shields.io/maven-central/v/io.mockk/mockk.svg?label=release)](http://search.maven.org/#search%7Cga%7C1%7Cmockk) +[![Relase Version](https://img.shields.io/maven-central/v/io.mockk/mockk.svg?label=release)](https://search.maven.org/#search%7Cga%7C1%7Cmockk) [![Change log](https://img.shields.io/badge/change%20log-%E2%96%A4-yellow.svg)](https://github.com/mockk/mockk/releases) [![codecov](https://codecov.io/gh/mockk/mockk/branch/master/graph/badge.svg)](https://codecov.io/gh/mockk/mockk) [![Android](https://img.shields.io/badge/android-support-green.svg)](https://mockk.io/ANDROID) @@ -1397,7 +1397,7 @@ This project exists thanks to all the people who contribute. To ask questions, please use Stack Overflow or Gitter. * Chat/Gitter: [https://gitter.im/mockk-io/Lobby](https://gitter.im/mockk-io/Lobby) -* Stack Overflow: [http://stackoverflow.com/questions/tagged/mockk](http://stackoverflow.com/questions/tagged/mockk) +* Stack Overflow: [http://stackoverflow.com/questions/tagged/mockk](https://stackoverflow.com/questions/tagged/mockk) To report bugs, please use the GitHub project. diff --git a/gradle/upload.gradle b/gradle/upload.gradle index 32fce7923..d128b0baf 100644 --- a/gradle/upload.gradle +++ b/gradle/upload.gradle @@ -28,7 +28,7 @@ afterEvaluate { pom { name = mavenName description = mavenDescription - url = 'http://mockk.io' + url = 'https://mockk.io' scm { connection = 'scm:git:git@github.com:mockk/mockk.git' @@ -52,7 +52,7 @@ afterEvaluate { licenses { license { name = 'Apache License, Version 2.0' - url = 'http://www.apache.org/licenses/LICENSE-2.0' + url = 'https://www.apache.org/licenses/LICENSE-2.0' } } } From c23a873b9ca10c8e3c126a7431b37e0d5c3aaa28 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 20 Jul 2022 23:51:33 +0200 Subject: [PATCH 04/36] change toUpper/LowerCase() to uppercase() and lowercase() --- dsl/common/src/main/kotlin/io/mockk/MockKSettings.kt | 2 +- .../main/kotlin/io/mockk/impl/recording/PermanentMocker.kt | 4 ++-- .../impl/recording/states/StubbingAwaitingAnswerState.kt | 4 ++-- mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dsl/common/src/main/kotlin/io/mockk/MockKSettings.kt b/dsl/common/src/main/kotlin/io/mockk/MockKSettings.kt index 29eadaa8d..608ae7157 100644 --- a/dsl/common/src/main/kotlin/io/mockk/MockKSettings.kt +++ b/dsl/common/src/main/kotlin/io/mockk/MockKSettings.kt @@ -19,7 +19,7 @@ enum class StackTracesAlignment { fun stackTracesAlignmentValueOf(property: String): StackTracesAlignment { return try { - enumValueOf(property.toUpperCase()) + enumValueOf(property.uppercase()) } catch (e: IllegalArgumentException) { StackTracesAlignment.CENTER } diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/PermanentMocker.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/PermanentMocker.kt index af2d31d8a..0601f94d0 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/PermanentMocker.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/PermanentMocker.kt @@ -138,11 +138,11 @@ class PermanentMocker( args.isEmpty() ) { return prefix + - methodName[3].toLowerCase() + + methodName[3].lowercase() + methodName.substring(4) } return prefix + methodName + "(" + args.joinToString(", ") + ")" } -} \ No newline at end of file +} diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/StubbingAwaitingAnswerState.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/StubbingAwaitingAnswerState.kt index f11e37965..6a0cdb9ee 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/StubbingAwaitingAnswerState.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/StubbingAwaitingAnswerState.kt @@ -71,5 +71,5 @@ class StubbingAwaitingAnswerState(recorder: CommonCallRecorder) : CallRecordingS } } - private fun String.toCamelCase() = if (isEmpty()) this else substring(0, 1).toLowerCase() + substring(1) -} \ No newline at end of file + private fun String.toCamelCase() = if (isEmpty()) this else substring(0, 1).lowercase() + substring(1) +} diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt index ba6442670..cb586d936 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt @@ -159,7 +159,7 @@ actual object InternalPlatform { fun isRunningAndroidInstrumentationTest(): Boolean { return System.getProperty("java.vendor", "") - .toLowerCase(Locale.US) + .lowercase(Locale.US) .contains("android") } From 2016d14f4efd1614f5c22afdde69cee64c992b62 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 20 Jul 2022 23:53:33 +0200 Subject: [PATCH 05/36] change Kt lang level to 1.5, update GitHub action to remove Kt 1.4 and add 1.7 --- .github/workflows/gradle.yml | 7 +------ build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 14cbd8f83..5c3c90b35 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -11,13 +11,8 @@ jobs: strategy: matrix: java-version: [11, 12, 13, 14, 15, 16, 17] - kotlin-version: [1.4.32, 1.5.31, 1.6.0] + kotlin-version: [1.5.31, 1.6.0, 1.7.10] kotlin-ir-enabled: [true, false] - exclude: - - kotlin-version: 1.4.32 - java-version: 16 - - kotlin-version: 1.4.32 - java-version: 17 # in case one JDK fails, we still want to see results from others fail-fast: false runs-on: ubuntu-latest diff --git a/build.gradle b/build.gradle index acadb5bee..b8998b085 100644 --- a/build.gradle +++ b/build.gradle @@ -40,8 +40,8 @@ subprojects { subProject -> tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { jvmTarget = "1.8" - apiVersion = "1.7" - languageVersion = "1.7" + apiVersion = "1.5" + languageVersion = "1.5" } } } From c090846874968eb686a710fab5510b5845715e44 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Thu, 21 Jul 2022 10:13:08 +0200 Subject: [PATCH 06/36] remove invalid 'flowRoot' from SVGs, resize documents to contents --- doc/icon.svg | 141 +++++++++++++++++++++++++-------------------------- doc/logo.svg | 141 ++++++++++++++++++++++++--------------------------- doc/new.svg | 87 +++++++++++++++---------------- 3 files changed, 175 insertions(+), 194 deletions(-) diff --git a/doc/icon.svg b/doc/icon.svg index 16bf3da52..979da5232 100644 --- a/doc/icon.svg +++ b/doc/icon.svg @@ -2,20 +2,20 @@ + inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)" + sodipodi:docname="icon.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + inkscape:window-width="1277" + inkscape:window-height="1528" + inkscape:window-x="1273" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:pagecheckerboard="0" + fit-margin-left="5" + fit-margin-top="5" + fit-margin-right="5" + fit-margin-bottom="5" /> @@ -44,65 +49,55 @@ image/svg+xml - - - - - - - + + + + + + + + K + id="text892">K + diff --git a/doc/logo.svg b/doc/logo.svg index d6a6e18bf..61d515b38 100644 --- a/doc/logo.svg +++ b/doc/logo.svg @@ -2,23 +2,23 @@ + inkscape:export-ydpi="99.209999" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + inkscape:window-width="1277" + inkscape:window-height="1528" + inkscape:window-x="1273" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:pagecheckerboard="0" + units="px" /> @@ -47,7 +49,6 @@ image/svg+xml - @@ -55,57 +56,47 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1"> - - - - - - MockK + + + + + + + + MockK + diff --git a/doc/new.svg b/doc/new.svg index f40262541..cd7b59635 100644 --- a/doc/new.svg +++ b/doc/new.svg @@ -2,20 +2,20 @@ + inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)" + sodipodi:docname="new.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + inkscape:window-width="1277" + inkscape:window-height="1528" + inkscape:window-x="1273" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:pagecheckerboard="0" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" /> @@ -44,37 +49,27 @@ image/svg+xml - - - - NEW + id="layer1" + transform="translate(-52.967503,-77.017563)"> + + + + + From ade76643598cdcd972554e3660b800954cbb5b8d Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Thu, 21 Jul 2022 10:31:47 +0200 Subject: [PATCH 07/36] add AXIS typeface, and re-align logo&icon --- doc/font.txt | 2 - doc/icon.svg | 79 +++++++++++++++---------------- doc/logo.svg | 77 +++++++++++++++--------------- doc/typefaces/Axis Extrabold.otf | Bin 0 -> 24532 bytes doc/typefaces/typefaces.md | 7 +++ 5 files changed, 82 insertions(+), 83 deletions(-) delete mode 100644 doc/font.txt create mode 100644 doc/typefaces/Axis Extrabold.otf create mode 100644 doc/typefaces/typefaces.md diff --git a/doc/font.txt b/doc/font.txt deleted file mode 100644 index 20dfc42c9..000000000 --- a/doc/font.txt +++ /dev/null @@ -1,2 +0,0 @@ -https://www.behance.net/gallery/17890579/AXIS-Typeface - diff --git a/doc/icon.svg b/doc/icon.svg index 979da5232..e82b4b26c 100644 --- a/doc/icon.svg +++ b/doc/icon.svg @@ -26,8 +26,8 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="2.2999579" - inkscape:cx="85.001557" - inkscape:cy="94.132159" + inkscape:cx="85.436346" + inkscape:cy="94.132158" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" @@ -58,46 +58,43 @@ id="layer1" transform="translate(-63.497518,-64.382512)"> - - - - - - - K + + + + + K diff --git a/doc/logo.svg b/doc/logo.svg index 61d515b38..018410b39 100644 --- a/doc/logo.svg +++ b/doc/logo.svg @@ -30,7 +30,7 @@ inkscape:pageshadow="2" inkscape:zoom="1.1499789" inkscape:cx="188.26432" - inkscape:cy="281.3095" + inkscape:cy="281.30951" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" @@ -57,46 +57,43 @@ inkscape:groupmode="layer" id="layer1"> - - - - - - - MockK + + + + + MockK diff --git a/doc/typefaces/Axis Extrabold.otf b/doc/typefaces/Axis Extrabold.otf new file mode 100644 index 0000000000000000000000000000000000000000..9fe3776387f17efcb31b65d98fce31f22b051d1a GIT binary patch literal 24532 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 Date: Thu, 21 Jul 2022 10:35:42 +0200 Subject: [PATCH 08/36] undo formatting + apply IntelliJ suggestion --- .../io/mockk/impl/recording/states/VerifyingState.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt index c1eeeb747..59e51f40e 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt @@ -92,14 +92,11 @@ class VerifyingState( if (calledStubs.size == 1) { val calledStub = calledStubs[0] throw AssertionError(recorder.safeExec { - "Verification failed: ${calledStub.toStr()} should not be called:\n" + - calledStub.allRecordedCalls().joinToString("\n") + "Verification failed: ${calledStub.toStr()} should not be called:\n" + calledStub.allRecordedCalls().joinToString("\n") }) } else { throw AssertionError(recorder.safeExec { - "Verification failed: ${ - calledStubs.map { it.toStr() }.joinToString(", ") - } should not be called:\n" + + "Verification failed: ${calledStubs.joinToString(", ") { it.toStr() }} should not be called:\n" + calledStubs.flatMap { it.allRecordedCalls() }.joinToString("\n") }) } From 29ac88c27916860953bed52778e07a8104d16161 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Thu, 21 Jul 2022 10:36:44 +0200 Subject: [PATCH 09/36] minor formatting --- .../io/mockk/impl/recording/states/VerifyingState.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt index 59e51f40e..70c24f0ac 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt @@ -56,7 +56,7 @@ class VerifyingState( private fun failIfNotPassed(outcome: VerificationResult, inverse: Boolean) { if (inverse) { when (outcome) { - is VerificationResult.OK -> { + is VerificationResult.OK -> { val callsReport = VerificationHelpers.formatCalls(outcome.verifiedCalls) throw AssertionError("Inverse verification failed.\n\nVerified calls:\n$callsReport") } @@ -65,7 +65,7 @@ class VerifyingState( } else { when (outcome) { is VerificationResult.Failure -> throw AssertionError("Verification failed: ${outcome.message}") - is VerificationResult.OK -> {} + is VerificationResult.OK -> {} } } } @@ -92,7 +92,8 @@ class VerifyingState( if (calledStubs.size == 1) { val calledStub = calledStubs[0] throw AssertionError(recorder.safeExec { - "Verification failed: ${calledStub.toStr()} should not be called:\n" + calledStub.allRecordedCalls().joinToString("\n") + "Verification failed: ${calledStub.toStr()} should not be called:\n" + + calledStub.allRecordedCalls().joinToString("\n") }) } else { throw AssertionError(recorder.safeExec { From 2ca2423c65aa76480b724af6e0a3d8dd0fa0fd44 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Thu, 21 Jul 2022 10:37:39 +0200 Subject: [PATCH 10/36] simplify when statement --- .../impl/recording/states/VerifyingState.kt | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt index 70c24f0ac..b251833d7 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/VerifyingState.kt @@ -54,18 +54,13 @@ class VerifyingState( } private fun failIfNotPassed(outcome: VerificationResult, inverse: Boolean) { - if (inverse) { - when (outcome) { - is VerificationResult.OK -> { - val callsReport = VerificationHelpers.formatCalls(outcome.verifiedCalls) - throw AssertionError("Inverse verification failed.\n\nVerified calls:\n$callsReport") - } - is VerificationResult.Failure -> {} + when (outcome) { + is VerificationResult.OK -> if (inverse) { + val callsReport = VerificationHelpers.formatCalls(outcome.verifiedCalls) + throw AssertionError("Inverse verification failed.\n\nVerified calls:\n$callsReport") } - } else { - when (outcome) { - is VerificationResult.Failure -> throw AssertionError("Verification failed: ${outcome.message}") - is VerificationResult.OK -> {} + is VerificationResult.Failure -> if (!inverse) { + throw AssertionError("Verification failed: ${outcome.message}") } } } From f100de48b00b328b6f9936bd4fccfeee967d88d8 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Thu, 21 Jul 2022 10:43:07 +0200 Subject: [PATCH 11/36] remove unnecessary spread operator --- mockk/common/src/main/kotlin/io/mockk/MockK.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mockk/common/src/main/kotlin/io/mockk/MockK.kt b/mockk/common/src/main/kotlin/io/mockk/MockK.kt index 03925b817..c94315735 100644 --- a/mockk/common/src/main/kotlin/io/mockk/MockK.kt +++ b/mockk/common/src/main/kotlin/io/mockk/MockK.kt @@ -342,7 +342,7 @@ fun clearMocks( MockK.useImpl { MockKDsl.internalClearMocks( firstMock = firstMock, - mocks = *mocks, + mocks = mocks, answers = answers, recordedCalls = recordedCalls, childMocks = childMocks, From 514e01420db260f67803fd14ef46ef62db1bd210 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 22 Jul 2022 18:41:06 +0200 Subject: [PATCH 12/36] add support for value class any() matcher --- .../main/kotlin/io/mockk/ValueClassSupport.kt | 11 ---- .../kotlin/io/mockk/InternalPlatformDsl.kt | 23 ++++++++- .../src/main/kotlin/io/mockk/Matchers.kt | 6 +-- .../kotlin/io/mockk/InternalPlatformDsl.kt | 50 ++++++++++++++++++- .../impl/recording/SignatureValueGenerator.kt | 10 +++- .../impl/recording/states/RecordingState.kt | 16 +++--- .../test/kotlin/io/mockk/it/ValueClassTest.kt | 6 ++- .../recording/JvmSignatureValueGenerator.kt | 31 ++++++++++-- 8 files changed, 121 insertions(+), 32 deletions(-) diff --git a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt index 1781141b5..2496d1e19 100644 --- a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -72,14 +72,3 @@ private fun KClass.isValueClass() = try { } catch (_: Throwable) { false } - -/** - * POLYFILL for kotlin version < 1.5 - * will be shadowed by implementation in kotlin SDK 1.5+ - * - * @return true if this is an inline class, else false - */ -private val KClass.isValue: Boolean - get() = !isData && - primaryConstructor?.parameters?.size == 1 && - java.declaredMethods.any { it.name == "box-impl" } diff --git a/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt b/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt index ddea66820..44dfb3682 100644 --- a/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt +++ b/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt @@ -1,6 +1,7 @@ package io.mockk import kotlin.coroutines.Continuation +import kotlin.reflect.KClass expect object InternalPlatformDsl { fun identityHashCode(obj: Any): Int @@ -35,6 +36,26 @@ expect object InternalPlatformDsl { fun counter(): InternalCounter fun coroutineCall(lambda: suspend () -> T): CoroutineCall + + /** + * Get the [KClass] of the single value that a `value class` contains. + * + * The result might also be a value class! So check recursively, if necessary. + * + * @return [KClass] of boxed value, if this is `value class`, else [cls]. + */ + fun unboxClass(cls: KClass<*>): KClass<*> + + /** + * Normally this simply casts [arg] to `T` + * + * However, if `T` is a `value class`es (of type [cls]) it will construct a new instance of the + * class, and set [arg] as the value. + */ + actual fun boxCast( + cls: KClass<*>, + arg: Any, + ): T } interface CoroutineCall { @@ -50,4 +71,4 @@ interface InternalCounter { val value: Long fun increment(): Long -} \ No newline at end of file +} diff --git a/dsl/common/src/main/kotlin/io/mockk/Matchers.kt b/dsl/common/src/main/kotlin/io/mockk/Matchers.kt index c9666ee08..100cad605 100644 --- a/dsl/common/src/main/kotlin/io/mockk/Matchers.kt +++ b/dsl/common/src/main/kotlin/io/mockk/Matchers.kt @@ -140,17 +140,16 @@ data class CaptureNullableMatcher( */ data class CapturingSlotMatcher( val captureSlot: CapturingSlot, - override val argumentType: KClass<*> + override val argumentType: KClass<*>, ) : Matcher, CapturingMatcher, TypedMatcher, EquivalentMatcher { override fun equivalent(): Matcher = ConstantMatcher(true) - @Suppress("UNCHECKED_CAST") override fun capture(arg: Any?) { if (arg == null) { captureSlot.isNull = true } else { captureSlot.isNull = false - captureSlot.captured = arg as T + captureSlot.captured = InternalPlatformDsl.boxCast(argumentType, arg) } captureSlot.isCaptured = true } @@ -473,4 +472,3 @@ fun CompositeMatcher<*>.captureSubMatchers(arg: Any?) { .forEach { it.capture(arg) } } } - diff --git a/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt b/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt index 6854e59d9..fdf1228b9 100644 --- a/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt +++ b/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt @@ -1,6 +1,5 @@ package io.mockk -import kotlinx.coroutines.runBlocking import java.lang.reflect.AccessibleObject import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method @@ -13,10 +12,13 @@ import kotlin.reflect.KProperty1 import kotlin.reflect.KType import kotlin.reflect.KTypeParameter import kotlin.reflect.full.allSuperclasses +import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.functions import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.javaMethod +import kotlinx.coroutines.runBlocking actual object InternalPlatformDsl { actual fun identityHashCode(obj: Any): Int = System.identityHashCode(obj) @@ -214,6 +216,29 @@ actual object InternalPlatformDsl { } actual fun coroutineCall(lambda: suspend () -> T): CoroutineCall = JvmCoroutineCall(lambda) + + actual fun unboxClass(cls: KClass<*>): KClass<*> { + if (!cls.isValue) return cls + + // get backing field + val backingField = cls.valueField() + + // get boxed value + return backingField.returnType.classifier as KClass<*> + } + + @Suppress("UNCHECKED_CAST") + actual fun boxCast( + cls: KClass<*>, + arg: Any, + ): T { + return if (cls.isValue) { + val constructor = cls.primaryConstructor!!.apply { isAccessible = true } + constructor.call(arg) as T + } else { + arg as T + } + } } class JvmCoroutineCall(private val lambda: suspend () -> T) : CoroutineCall { @@ -232,3 +257,26 @@ class JvmCoroutineCall(private val lambda: suspend () -> T) : CoroutineCall, KProperty1>() + +private fun KClass.valueField(): KProperty1 { + @Suppress("UNCHECKED_CAST") + return valueClassFieldCache.getOrPut(this) { + require(isValue) { "$this is not a value class" } + + // value classes always have a primary constructor... + val constructor = primaryConstructor!!.apply { isAccessible = true } + // ...and exactly one constructor parameter + val constructorParameter = constructor.parameters.first() + // ...with a backing field + val backingField = declaredMemberProperties + .first { it.name == constructorParameter.name } + .apply { isAccessible = true } + + backingField + } as KProperty1 +} diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureValueGenerator.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureValueGenerator.kt index 53f21cc14..5605a413e 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureValueGenerator.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureValueGenerator.kt @@ -1,7 +1,13 @@ package io.mockk.impl.recording +import io.mockk.impl.instantiation.AbstractInstantiator +import io.mockk.impl.instantiation.AnyValueGenerator import kotlin.reflect.KClass interface SignatureValueGenerator { - fun signatureValue(cls: KClass, orInstantiateVia: () -> T): T -} \ No newline at end of file + fun signatureValue( + cls: KClass, + anyValueGeneratorProvider: () -> AnyValueGenerator, + instantiator: AbstractInstantiator, + ): T +} diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt index b58b45b23..e78bb1b94 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt @@ -42,15 +42,17 @@ abstract class RecordingState(recorder: CommonCallRecorder) : CallRecordingState recorder.calls.addAll(detector.calls) } - @Suppress("UNCHECKED_CAST") override fun matcher(matcher: Matcher<*>, cls: KClass): T { - val signatureValue = recorder.signatureValueGenerator.signatureValue(cls) { - recorder.anyValueGenerator().anyValue(cls, isNullable = false) { - recorder.instantiator.instantiate(cls) - } as T - } + val signatureValue = recorder.signatureValueGenerator.signatureValue( + cls, + recorder.anyValueGenerator, + recorder.instantiator, + ) + + val packRef: Any = InternalPlatform.packRef(signatureValue) + ?: error("null packRef for $cls signature $signatureValue") - builder().addMatcher(matcher, InternalPlatform.packRef(signatureValue)!!) + builder().addMatcher(matcher, packRef) return signatureValue } diff --git a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt index 66fa6bec6..cb1d4869e 100644 --- a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt +++ b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt @@ -2,6 +2,7 @@ package io.mockk.it import io.mockk.* import kotlin.jvm.JvmInline +import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals @@ -57,6 +58,7 @@ class ValueClassTest { } @Test + @Ignore // TODO I had this working at one point... commenting it out now to see if the tests pass on CI/CD fun `slot for value class`() { val mock = mockk(relaxed = true) val slot = slot() @@ -84,9 +86,9 @@ class ValueClassTest { } // TODO should be value class in kotlin 1.5+ -private inline class DummyValue(val value: Int) +inline class DummyValue(val value: Int) -private class DummyService { +class DummyService { fun requestValue() = DummyValue(0) diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/recording/JvmSignatureValueGenerator.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/recording/JvmSignatureValueGenerator.kt index 07b1023ae..6428d98e7 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/recording/JvmSignatureValueGenerator.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/recording/JvmSignatureValueGenerator.kt @@ -1,11 +1,29 @@ package io.mockk.impl.recording -import java.util.* +import io.mockk.InternalPlatformDsl +import io.mockk.impl.instantiation.AbstractInstantiator +import io.mockk.impl.instantiation.AnyValueGenerator +import java.util.Random import kotlin.reflect.KClass import kotlin.reflect.full.cast +import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.isAccessible class JvmSignatureValueGenerator(val rnd: Random) : SignatureValueGenerator { - override fun signatureValue(cls: KClass, orInstantiateVia: () -> T): T { + override fun signatureValue( + cls: KClass, + anyValueGeneratorProvider: () -> AnyValueGenerator, + instantiator: AbstractInstantiator, + ): T { + + if (cls.isValue) { + val valueCls = InternalPlatformDsl.unboxClass(cls) + val valueSig = signatureValue(valueCls, anyValueGeneratorProvider, instantiator) + + val constructor = cls.primaryConstructor!!.apply { isAccessible = true } + return constructor.call(valueSig) + } + return cls.cast( when (cls) { java.lang.Boolean::class -> rnd.nextBoolean() @@ -17,8 +35,13 @@ class JvmSignatureValueGenerator(val rnd: Random) : SignatureValueGenerator { java.lang.Float::class -> rnd.nextFloat() java.lang.Double::class -> rnd.nextDouble() java.lang.String::class -> rnd.nextLong().toString(16) - else -> orInstantiateVia() + + else -> + @Suppress("UNCHECKED_CAST") + anyValueGeneratorProvider().anyValue(cls, isNullable = false) { + instantiator.instantiate(cls) + } as T } ) } -} \ No newline at end of file +} From cd368da269d56da179a409ae54d7990bcf04fd8b Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 22 Jul 2022 18:49:02 +0200 Subject: [PATCH 13/36] fix value classes & slots --- dsl/common/src/main/kotlin/io/mockk/API.kt | 10 ++++++---- .../src/test/kotlin/io/mockk/it/ValueClassTest.kt | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dsl/common/src/main/kotlin/io/mockk/API.kt b/dsl/common/src/main/kotlin/io/mockk/API.kt index 2c095eaff..0c802aceb 100644 --- a/dsl/common/src/main/kotlin/io/mockk/API.kt +++ b/dsl/common/src/main/kotlin/io/mockk/API.kt @@ -3557,11 +3557,13 @@ interface TypedMatcher { val argumentType: KClass<*> fun checkType(arg: Any?): Boolean { - if (argumentType.simpleName === null) { - return true + return when { + argumentType.simpleName === null -> true + else -> { + val unboxedClass = InternalPlatformDsl.unboxClass(argumentType) + return unboxedClass.isInstance(arg) + } } - - return argumentType.isInstance(arg) } } diff --git a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt index cb1d4869e..d09b162a1 100644 --- a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt +++ b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt @@ -58,7 +58,6 @@ class ValueClassTest { } @Test - @Ignore // TODO I had this working at one point... commenting it out now to see if the tests pass on CI/CD fun `slot for value class`() { val mock = mockk(relaxed = true) val slot = slot() From 32ade90a2e7dba3f65a891b3bccc2c3e99b5c132 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 22 Jul 2022 19:11:26 +0200 Subject: [PATCH 14/36] update 'inline class' to 'value class' --- mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt index d09b162a1..a066ef2cd 100644 --- a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt +++ b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt @@ -2,7 +2,6 @@ package io.mockk.it import io.mockk.* import kotlin.jvm.JvmInline -import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals @@ -84,8 +83,8 @@ class ValueClassTest { } } -// TODO should be value class in kotlin 1.5+ -inline class DummyValue(val value: Int) +@JvmInline +value class DummyValue(val value: Int) class DummyService { From 0ffba57163a687a04acb72d4c7d2b36abd03b677 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 22 Jul 2022 19:16:46 +0200 Subject: [PATCH 15/36] update TODO, apply some auto-fixes to remove warnings --- dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt b/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt index fdf1228b9..b9c1e7d4f 100644 --- a/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt +++ b/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt @@ -46,7 +46,7 @@ actual object InternalPlatformDsl { kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED -> "SUSPEND_MARKER" is Continuation<*> -> "continuation {}" is KClass<*> -> this.simpleName ?: "" - is Method -> name + "(" + parameterTypes.map { it.simpleName }.joinToString() + ")" + is Method -> name + "(" + parameterTypes.joinToString { it.simpleName } + ")" is Function<*> -> "lambda {}" else -> toString() } @@ -243,7 +243,7 @@ actual object InternalPlatformDsl { class JvmCoroutineCall(private val lambda: suspend () -> T) : CoroutineCall { companion object { - val callMethod = JvmCoroutineCall::class.java.getMethod("callCoroutine", Continuation::class.java) + val callMethod: Method = JvmCoroutineCall::class.java.getMethod("callCoroutine", Continuation::class.java) } suspend fun callCoroutine() = lambda() @@ -258,8 +258,7 @@ class JvmCoroutineCall(private val lambda: suspend () -> T) : CoroutineCall, KProperty1>() From f2e6c7ad9ee3fd553dd958cace459c6e634b2a60 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 23 Jul 2022 11:04:45 +0200 Subject: [PATCH 16/36] refactor mock-dsl-jvm to re-use ValueClassSupport code, and add TODOs to de-dupe them #857 --- .../main/kotlin/io/mockk/ValueClassSupport.kt | 3 + .../main/kotlin/io/mockk/ValueClassSupport.kt | 3 + .../kotlin/io/mockk/InternalPlatformDsl.kt | 33 +------ .../main/kotlin/io/mockk/ValueClassSupport.kt | 85 +++++++++++++++++++ 4 files changed, 93 insertions(+), 31 deletions(-) create mode 100644 dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt diff --git a/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt b/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt index 1781141b5..a3de5d4fd 100644 --- a/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -6,6 +6,9 @@ import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.isAccessible +// TODO this class is copy-pasted and should be de-duplicated +// see https://github.com/mockk/mockk/issues/857 + private val valueClassFieldCache = mutableMapOf, KProperty1>() /** diff --git a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt index 2496d1e19..fb0982cef 100644 --- a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -6,6 +6,9 @@ import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.isAccessible +// TODO this class is copy-pasted and should be de-duplicated +// see https://github.com/mockk/mockk/issues/857 + private val valueClassFieldCache = mutableMapOf, KProperty1>() /** diff --git a/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt b/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt index b9c1e7d4f..59a248c97 100644 --- a/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt +++ b/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt @@ -1,5 +1,6 @@ package io.mockk +import io.mockk.ValueClassSupportDsl.boxedClass import java.lang.reflect.AccessibleObject import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method @@ -217,15 +218,7 @@ actual object InternalPlatformDsl { actual fun coroutineCall(lambda: suspend () -> T): CoroutineCall = JvmCoroutineCall(lambda) - actual fun unboxClass(cls: KClass<*>): KClass<*> { - if (!cls.isValue) return cls - - // get backing field - val backingField = cls.valueField() - - // get boxed value - return backingField.returnType.classifier as KClass<*> - } + actual fun unboxClass(cls: KClass<*>): KClass<*> = cls.boxedClass() @Suppress("UNCHECKED_CAST") actual fun boxCast( @@ -257,25 +250,3 @@ class JvmCoroutineCall(private val lambda: suspend () -> T) : CoroutineCall, KProperty1>() - -private fun KClass.valueField(): KProperty1 { - @Suppress("UNCHECKED_CAST") - return valueClassFieldCache.getOrPut(this) { - require(isValue) { "$this is not a value class" } - - // value classes always have a primary constructor... - val constructor = primaryConstructor!!.apply { isAccessible = true } - // ...and exactly one constructor parameter - val constructorParameter = constructor.parameters.first() - // ...with a backing field - val backingField = declaredMemberProperties - .first { it.name == constructorParameter.name } - .apply { isAccessible = true } - - backingField - } as KProperty1 -} diff --git a/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt b/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt new file mode 100644 index 000000000..626cf9fe2 --- /dev/null +++ b/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -0,0 +1,85 @@ +package io.mockk + +import kotlin.reflect.KClass +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.isAccessible + +/** + * Provides value class support in the `mockk-dsl-jvm` subproject. + * + * This is marked as internal so that it won't clash with the another class in `mockk-agent-jvm`. + * + * TODO this class is copy-pasted and should be de-duplicated, see https://github.com/mockk/mockk/issues/857 + */ +internal object ValueClassSupportDsl { + + private val valueClassFieldCache = mutableMapOf, KProperty1>() + + /** + * Get boxed value of any value class + * + * @return boxed value of value class, if this is value class, else just itself + */ + fun T.boxedValue(): Any? { + if (!this::class.isValueClass()) return this + + // get backing field + val backingField = this::class.valueField() + + // get boxed value + @Suppress("UNCHECKED_CAST") + return (backingField as KProperty1).get(this) + } + + /** + * Get class of boxed value of any value class + * + * @return class of boxed value, if this is value class, else just class of itself + */ + fun T.boxedClass(): KClass<*> { + return this::class.boxedClass() + } + + /** + * Get the KClass of boxed value if this is a value class. + * + * @return class of boxed value, if this is value class, else just class of itself + */ + fun KClass<*>.boxedClass(): KClass<*> { + if (!this.isValueClass()) return this + + // get backing field + val backingField = this.valueField() + + // get boxed value + return backingField.returnType.classifier as KClass<*> + } + + + private fun KClass.valueField(): KProperty1 { + @Suppress("UNCHECKED_CAST") + return valueClassFieldCache.getOrPut(this) { + require(isValue) { "$this is not a value class" } + + // value classes always have a primary constructor... + val constructor = primaryConstructor!! + // ...and exactly one constructor parameter + val constructorParameter = constructor.parameters.first() + // ...with a backing field + val backingField = declaredMemberProperties + .first { it.name == constructorParameter.name } + .apply { isAccessible = true } + + backingField + } as KProperty1 + } + + private fun KClass.isValueClass() = try { + this.isValue + } catch (_: Throwable) { + false + } + +} From 1c5e442e47aa0ec4f9fc903011aa6592a8ed657e Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 23 Jul 2022 11:10:13 +0200 Subject: [PATCH 17/36] test for #729 --- .../test/kotlin/io/mockk/it/ValueClassTest.kt | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt index a066ef2cd..fd5988bb1 100644 --- a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt +++ b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt @@ -1,6 +1,9 @@ package io.mockk.it -import io.mockk.* +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import io.mockk.verify import kotlin.jvm.JvmInline import kotlin.test.Test import kotlin.test.assertEquals @@ -81,6 +84,17 @@ class ValueClassTest { assertEquals(givenResult, result) } + + @Test + fun `verify function with UInt return can be stubbed`() { + val mock = mockk { + every { getUInt() } returns 999u + } + + val result = mock.getUInt() + + assertEquals(999u, result) + } } @JvmInline @@ -88,9 +102,11 @@ value class DummyValue(val value: Int) class DummyService { - fun requestValue() = DummyValue(0) + fun requestValue() = DummyValue(0) + + fun processValue(value: DummyValue) = DummyValue(0) - fun processValue(value: DummyValue) = DummyValue(0) + fun getUInt(): UInt = 123u } @JvmInline From 9cc892fafc4d5b461d632d1aaae07998131143b9 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 23 Jul 2022 11:26:55 +0200 Subject: [PATCH 18/36] test #729, extension fun with UInt return --- .../src/test/kotlin/io/mockk/it/ValueClassTest.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt index fd5988bb1..a0681b77d 100644 --- a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt +++ b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt @@ -85,6 +85,7 @@ class ValueClassTest { assertEquals(givenResult, result) } + /** https://github.com/mockk/mockk/issues/729 */ @Test fun `verify function with UInt return can be stubbed`() { val mock = mockk { @@ -95,6 +96,19 @@ class ValueClassTest { assertEquals(999u, result) } + + /** https://github.com/mockk/mockk/issues/729 */ + @Test + fun `verify extension function with UInt return can be stubbed`() { + + val fn = mockk UInt>() + + every { "string".fn() } returns 777u + + val result = "string".fn() + + assertEquals(777u, result) + } } @JvmInline From bcd427efb63c7bcad30655c03c9410165cb6ff40 Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Sat, 23 Jul 2022 11:43:42 +0200 Subject: [PATCH 19/36] rm 'actual' from 'expect' object --- dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt b/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt index 44dfb3682..f9ac847bc 100644 --- a/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt +++ b/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt @@ -52,7 +52,7 @@ expect object InternalPlatformDsl { * However, if `T` is a `value class`es (of type [cls]) it will construct a new instance of the * class, and set [arg] as the value. */ - actual fun boxCast( + fun boxCast( cls: KClass<*>, arg: Any, ): T From 201b43d3d27f4058d6e628d2b07c13af6dc9fdf3 Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Sat, 23 Jul 2022 14:44:45 +0200 Subject: [PATCH 20/36] fix kdoc typo --- dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt b/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt index f9ac847bc..c280a87cc 100644 --- a/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt +++ b/dsl/common/src/main/kotlin/io/mockk/InternalPlatformDsl.kt @@ -49,8 +49,8 @@ expect object InternalPlatformDsl { /** * Normally this simply casts [arg] to `T` * - * However, if `T` is a `value class`es (of type [cls]) it will construct a new instance of the - * class, and set [arg] as the value. + * However, if `T` is a `value class` (of type [cls]) this will construct a new instance of the + * value class, and set [arg] as the value. */ fun boxCast( cls: KClass<*>, From 65e864348848831ff2f6c3678e5c891a730af5fa Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Sat, 23 Jul 2022 14:45:24 +0200 Subject: [PATCH 21/36] fix kdoc typo --- dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt b/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt index 626cf9fe2..7aa2b90b6 100644 --- a/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -9,7 +9,7 @@ import kotlin.reflect.jvm.isAccessible /** * Provides value class support in the `mockk-dsl-jvm` subproject. * - * This is marked as internal so that it won't clash with the another class in `mockk-agent-jvm`. + * This is marked as internal so that it won't clash with the other class in `mockk-agent-jvm`. * * TODO this class is copy-pasted and should be de-duplicated, see https://github.com/mockk/mockk/issues/857 */ From 5e7851bea66bf348cdb1b5152baf7917ca587e1f Mon Sep 17 00:00:00 2001 From: Bengt Brodersen Date: Sun, 24 Jul 2022 00:41:39 +0200 Subject: [PATCH 22/36] refactor: simplify ValueClassSupport --- .../main/kotlin/io/mockk/ValueClassSupport.kt | 92 ++++--------------- .../io/mockk/proxy/android/advice/Advice.kt | 2 +- .../main/kotlin/io/mockk/ValueClassSupport.kt | 81 ++++------------ .../io/mockk/proxy/jvm/advice/Interceptor.kt | 2 +- .../kotlin/io/mockk/impl/InternalPlatform.kt | 2 +- .../instantiation/JvmMockFactoryHelper.kt | 2 +- 6 files changed, 44 insertions(+), 137 deletions(-) diff --git a/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt b/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt index a3de5d4fd..cea2d7b42 100644 --- a/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -3,86 +3,34 @@ package io.mockk import kotlin.reflect.KClass import kotlin.reflect.KProperty1 import kotlin.reflect.full.declaredMemberProperties -import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.isAccessible -// TODO this class is copy-pasted and should be de-duplicated -// see https://github.com/mockk/mockk/issues/857 - -private val valueClassFieldCache = mutableMapOf, KProperty1>() - /** - * Get boxed value of any value class - * - * @return boxed value of value class, if this is value class, else just itself + * Underlying property value of a **`value class`** or self */ -fun T.boxedValue(): Any? { - if (!this::class.isValueClass()) return this - - // get backing field - val backingField = this::class.valueField() - - // get boxed value +val T.boxedValue: Any? @Suppress("UNCHECKED_CAST") - return (backingField as KProperty1).get(this) -} + get() = if (!this::class.isValue_safe) this + else (this::class as KClass).boxedProperty.get(this) /** - * Get class of boxed value of any value class - * - * @return class of boxed value, if this is value class, else just class of itself + * Underlying property class of a **`value class`** or self */ -fun T.boxedClass(): KClass<*> { - return this::class.boxedClass() -} - -/** - * Get the KClass of boxed value if this is a value class. - * - * @return class of boxed value, if this is value class, else just class of itself - */ -fun KClass<*>.boxedClass(): KClass<*> { - if (!this.isValueClass()) return this - - // get backing field - val backingField = this.valueField() - - // get boxed value - return backingField.returnType.classifier as KClass<*> -} - - -private fun KClass.valueField(): KProperty1 { - @Suppress("UNCHECKED_CAST") - return valueClassFieldCache.getOrPut(this) { - require(isValue) { "$this is not a value class" } - - // value classes always have a primary constructor... - val constructor = primaryConstructor!! - // ...and exactly one constructor parameter - val constructorParameter = constructor.parameters.first() - // ...with a backing field - val backingField = declaredMemberProperties - .first { it.name == constructorParameter.name } - .apply { isAccessible = true } - - backingField - } as KProperty1 -} - -private fun KClass.isValueClass() = try { - this.isValue -} catch (_: Throwable) { - false -} +val KClass<*>.boxedClass: KClass<*> + get() = if (!this.isValue_safe) this + else this.boxedProperty.returnType.classifier as KClass<*> /** - * POLYFILL for kotlin version < 1.5 - * will be shadowed by implementation in kotlin SDK 1.5+ - * - * @return true if this is an inline class, else false + * Underlying property of a **`value class`** */ -private val KClass.isValue: Boolean - get() = !isData && - primaryConstructor?.parameters?.size == 1 && - java.declaredMethods.any { it.name == "box-impl" } +private val KClass.boxedProperty: KProperty1 + get() = if (!this.isValue_safe) throw UnsupportedOperationException("$this is not a value class") + // value classes always have exactly one property + else this.declaredMemberProperties.first().apply { isAccessible = true } + +private val KClass.isValue_safe: Boolean + get() = try { + this.isValue + } catch (_: UnsupportedOperationException) { + false + } diff --git a/agent/android/src/main/kotlin/io/mockk/proxy/android/advice/Advice.kt b/agent/android/src/main/kotlin/io/mockk/proxy/android/advice/Advice.kt index e1b9e18ab..4c4dd594f 100644 --- a/agent/android/src/main/kotlin/io/mockk/proxy/android/advice/Advice.kt +++ b/agent/android/src/main/kotlin/io/mockk/proxy/android/advice/Advice.kt @@ -81,7 +81,7 @@ internal class Advice( superMethodCall, arguments ) - ?.boxedValue() // unbox value class objects + ?.boxedValue // unbox value class objects } } diff --git a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt index fb0982cef..cea2d7b42 100644 --- a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -3,75 +3,34 @@ package io.mockk import kotlin.reflect.KClass import kotlin.reflect.KProperty1 import kotlin.reflect.full.declaredMemberProperties -import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.isAccessible -// TODO this class is copy-pasted and should be de-duplicated -// see https://github.com/mockk/mockk/issues/857 - -private val valueClassFieldCache = mutableMapOf, KProperty1>() - /** - * Get boxed value of any value class - * - * @return boxed value of value class, if this is value class, else just itself + * Underlying property value of a **`value class`** or self */ -fun T.boxedValue(): Any? { - if (!this::class.isValueClass()) return this - - // get backing field - val backingField = this::class.valueField() - - // get boxed value +val T.boxedValue: Any? @Suppress("UNCHECKED_CAST") - return (backingField as KProperty1).get(this) -} + get() = if (!this::class.isValue_safe) this + else (this::class as KClass).boxedProperty.get(this) /** - * Get class of boxed value of any value class - * - * @return class of boxed value, if this is value class, else just class of itself + * Underlying property class of a **`value class`** or self */ -fun T.boxedClass(): KClass<*> { - return this::class.boxedClass() -} +val KClass<*>.boxedClass: KClass<*> + get() = if (!this.isValue_safe) this + else this.boxedProperty.returnType.classifier as KClass<*> /** - * Get the KClass of boxed value if this is a value class. - * - * @return class of boxed value, if this is value class, else just class of itself + * Underlying property of a **`value class`** */ -fun KClass<*>.boxedClass(): KClass<*> { - if (!this.isValueClass()) return this - - // get backing field - val backingField = this.valueField() - - // get boxed value - return backingField.returnType.classifier as KClass<*> -} - - -private fun KClass.valueField(): KProperty1 { - @Suppress("UNCHECKED_CAST") - return valueClassFieldCache.getOrPut(this) { - require(isValue) { "$this is not a value class" } - - // value classes always have a primary constructor... - val constructor = primaryConstructor!! - // ...and exactly one constructor parameter - val constructorParameter = constructor.parameters.first() - // ...with a backing field - val backingField = declaredMemberProperties - .first { it.name == constructorParameter.name } - .apply { isAccessible = true } - - backingField - } as KProperty1 -} - -private fun KClass.isValueClass() = try { - this.isValue -} catch (_: Throwable) { - false -} +private val KClass.boxedProperty: KProperty1 + get() = if (!this.isValue_safe) throw UnsupportedOperationException("$this is not a value class") + // value classes always have exactly one property + else this.declaredMemberProperties.first().apply { isAccessible = true } + +private val KClass.isValue_safe: Boolean + get() = try { + this.isValue + } catch (_: UnsupportedOperationException) { + false + } diff --git a/agent/jvm/src/main/kotlin/io/mockk/proxy/jvm/advice/Interceptor.kt b/agent/jvm/src/main/kotlin/io/mockk/proxy/jvm/advice/Interceptor.kt index dcb8019c5..1ce1673cf 100644 --- a/agent/jvm/src/main/kotlin/io/mockk/proxy/jvm/advice/Interceptor.kt +++ b/agent/jvm/src/main/kotlin/io/mockk/proxy/jvm/advice/Interceptor.kt @@ -19,7 +19,7 @@ internal class Interceptor( method ) return handler.invocation(self, method, callOriginalMethod, arguments) - ?.boxedValue() // unbox value class objects + ?.boxedValue // unbox value class objects } } diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt index cb586d936..4b2dc598a 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt @@ -63,7 +63,7 @@ actual object InternalPlatform { actual fun packRef(arg: Any?): Any? { return when { arg == null -> null - isPassedByValue(arg.boxedClass()) -> arg.boxedValue() + isPassedByValue(arg::class.boxedClass) -> arg.boxedValue else -> ref(arg) } } diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt index 71552c367..b08b9250d 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt @@ -151,7 +151,7 @@ object JvmMockFactoryHelper { is KType -> kotlinReturnType.classifier as? KClass<*> ?: returnType.kotlin is KClass<*> -> kotlinReturnType else -> returnType.kotlin - }.boxedClass() + }.boxedClass val androidCompatibleReturnType = if (returnType.qualifiedName in androidUnsupportedTypes) { this@toDescription.returnType.kotlin From aafe7afd6512606f88f5057b79cb37663e91952d Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 24 Jul 2022 10:42:30 +0200 Subject: [PATCH 23/36] more testing for value classes - pre-merge in https://github.com/aSemy/mockk/pull/1 --- .../main/kotlin/io/mockk/ValueClassSupport.kt | 99 ++-- .../io/mockk/proxy/android/advice/Advice.kt | 2 +- .../main/kotlin/io/mockk/ValueClassSupport.kt | 94 ++-- .../io/mockk/proxy/jvm/advice/Interceptor.kt | 2 +- .../kotlin/io/mockk/InternalPlatformDsl.kt | 2 +- .../main/kotlin/io/mockk/ValueClassSupport.kt | 96 ++-- .../test/kotlin/io/mockk/it/ValueClassTest.kt | 478 +++++++++++++++--- .../kotlin/io/mockk/impl/InternalPlatform.kt | 2 +- .../instantiation/JvmMockFactoryHelper.kt | 2 +- 9 files changed, 545 insertions(+), 232 deletions(-) diff --git a/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt b/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt index a3de5d4fd..961634ead 100644 --- a/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -9,80 +9,57 @@ import kotlin.reflect.jvm.isAccessible // TODO this class is copy-pasted and should be de-duplicated // see https://github.com/mockk/mockk/issues/857 -private val valueClassFieldCache = mutableMapOf, KProperty1>() - /** - * Get boxed value of any value class + * Underlying property value of a **`value class`** or self. * - * @return boxed value of value class, if this is value class, else just itself + * The type of the return might also be a `value class`! */ -fun T.boxedValue(): Any? { - if (!this::class.isValueClass()) return this - - // get backing field - val backingField = this::class.valueField() - - // get boxed value +val T.boxedValue: Any? @Suppress("UNCHECKED_CAST") - return (backingField as KProperty1).get(this) -} + get() = if (!this::class.isValue_safe) { + this + } else { + (this::class as KClass).boxedProperty.get(this) + } /** - * Get class of boxed value of any value class + * Underlying property class of a **`value class`** or self. * - * @return class of boxed value, if this is value class, else just class of itself + * The returned class might also be a `value class`! */ -fun T.boxedClass(): KClass<*> { - return this::class.boxedClass() -} +val KClass<*>.boxedClass: KClass<*> + get() = if (!this.isValue_safe) { + this + } else { + this.boxedProperty.returnType.classifier as KClass<*> + } + +private val valueClassFieldCache = mutableMapOf, KProperty1>() /** - * Get the KClass of boxed value if this is a value class. + * Underlying property of a **`value class`**. * - * @return class of boxed value, if this is value class, else just class of itself + * The underlying property might also be a `value class`! */ -fun KClass<*>.boxedClass(): KClass<*> { - if (!this.isValueClass()) return this - - // get backing field - val backingField = this.valueField() - - // get boxed value - return backingField.returnType.classifier as KClass<*> -} - - -private fun KClass.valueField(): KProperty1 { - @Suppress("UNCHECKED_CAST") - return valueClassFieldCache.getOrPut(this) { - require(isValue) { "$this is not a value class" } - - // value classes always have a primary constructor... - val constructor = primaryConstructor!! - // ...and exactly one constructor parameter - val constructorParameter = constructor.parameters.first() - // ...with a backing field - val backingField = declaredMemberProperties - .first { it.name == constructorParameter.name } - .apply { isAccessible = true } - - backingField - } as KProperty1 -} - -private fun KClass.isValueClass() = try { - this.isValue -} catch (_: Throwable) { - false -} +private val KClass.boxedProperty: KProperty1 + get() = if (!this.isValue_safe) { + throw UnsupportedOperationException("$this is not a value class") + } else { + // value classes always have exactly one property + @Suppress("UNCHECKED_CAST") + valueClassFieldCache.getOrPut(this) { + this.declaredMemberProperties.first().apply { isAccessible = true } + } as KProperty1 + } /** - * POLYFILL for kotlin version < 1.5 - * will be shadowed by implementation in kotlin SDK 1.5+ + * Returns `true` if calling [KClass.isValue] is safe. * - * @return true if this is an inline class, else false + * (In some instances [KClass.isValue] can throw an exception.) */ -private val KClass.isValue: Boolean - get() = !isData && - primaryConstructor?.parameters?.size == 1 && - java.declaredMethods.any { it.name == "box-impl" } +private val KClass.isValue_safe: Boolean + get() = try { + this.isValue + } catch (_: UnsupportedOperationException) { + false + } diff --git a/agent/android/src/main/kotlin/io/mockk/proxy/android/advice/Advice.kt b/agent/android/src/main/kotlin/io/mockk/proxy/android/advice/Advice.kt index e1b9e18ab..4c4dd594f 100644 --- a/agent/android/src/main/kotlin/io/mockk/proxy/android/advice/Advice.kt +++ b/agent/android/src/main/kotlin/io/mockk/proxy/android/advice/Advice.kt @@ -81,7 +81,7 @@ internal class Advice( superMethodCall, arguments ) - ?.boxedValue() // unbox value class objects + ?.boxedValue // unbox value class objects } } diff --git a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt index fb0982cef..961634ead 100644 --- a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -9,69 +9,57 @@ import kotlin.reflect.jvm.isAccessible // TODO this class is copy-pasted and should be de-duplicated // see https://github.com/mockk/mockk/issues/857 -private val valueClassFieldCache = mutableMapOf, KProperty1>() - /** - * Get boxed value of any value class + * Underlying property value of a **`value class`** or self. * - * @return boxed value of value class, if this is value class, else just itself + * The type of the return might also be a `value class`! */ -fun T.boxedValue(): Any? { - if (!this::class.isValueClass()) return this - - // get backing field - val backingField = this::class.valueField() - - // get boxed value +val T.boxedValue: Any? @Suppress("UNCHECKED_CAST") - return (backingField as KProperty1).get(this) -} + get() = if (!this::class.isValue_safe) { + this + } else { + (this::class as KClass).boxedProperty.get(this) + } /** - * Get class of boxed value of any value class + * Underlying property class of a **`value class`** or self. * - * @return class of boxed value, if this is value class, else just class of itself + * The returned class might also be a `value class`! */ -fun T.boxedClass(): KClass<*> { - return this::class.boxedClass() -} +val KClass<*>.boxedClass: KClass<*> + get() = if (!this.isValue_safe) { + this + } else { + this.boxedProperty.returnType.classifier as KClass<*> + } + +private val valueClassFieldCache = mutableMapOf, KProperty1>() /** - * Get the KClass of boxed value if this is a value class. + * Underlying property of a **`value class`**. * - * @return class of boxed value, if this is value class, else just class of itself + * The underlying property might also be a `value class`! */ -fun KClass<*>.boxedClass(): KClass<*> { - if (!this.isValueClass()) return this - - // get backing field - val backingField = this.valueField() - - // get boxed value - return backingField.returnType.classifier as KClass<*> -} +private val KClass.boxedProperty: KProperty1 + get() = if (!this.isValue_safe) { + throw UnsupportedOperationException("$this is not a value class") + } else { + // value classes always have exactly one property + @Suppress("UNCHECKED_CAST") + valueClassFieldCache.getOrPut(this) { + this.declaredMemberProperties.first().apply { isAccessible = true } + } as KProperty1 + } - -private fun KClass.valueField(): KProperty1 { - @Suppress("UNCHECKED_CAST") - return valueClassFieldCache.getOrPut(this) { - require(isValue) { "$this is not a value class" } - - // value classes always have a primary constructor... - val constructor = primaryConstructor!! - // ...and exactly one constructor parameter - val constructorParameter = constructor.parameters.first() - // ...with a backing field - val backingField = declaredMemberProperties - .first { it.name == constructorParameter.name } - .apply { isAccessible = true } - - backingField - } as KProperty1 -} - -private fun KClass.isValueClass() = try { - this.isValue -} catch (_: Throwable) { - false -} +/** + * Returns `true` if calling [KClass.isValue] is safe. + * + * (In some instances [KClass.isValue] can throw an exception.) + */ +private val KClass.isValue_safe: Boolean + get() = try { + this.isValue + } catch (_: UnsupportedOperationException) { + false + } diff --git a/agent/jvm/src/main/kotlin/io/mockk/proxy/jvm/advice/Interceptor.kt b/agent/jvm/src/main/kotlin/io/mockk/proxy/jvm/advice/Interceptor.kt index dcb8019c5..1ce1673cf 100644 --- a/agent/jvm/src/main/kotlin/io/mockk/proxy/jvm/advice/Interceptor.kt +++ b/agent/jvm/src/main/kotlin/io/mockk/proxy/jvm/advice/Interceptor.kt @@ -19,7 +19,7 @@ internal class Interceptor( method ) return handler.invocation(self, method, callOriginalMethod, arguments) - ?.boxedValue() // unbox value class objects + ?.boxedValue // unbox value class objects } } diff --git a/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt b/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt index 59a248c97..1419a1367 100644 --- a/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt +++ b/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt @@ -218,7 +218,7 @@ actual object InternalPlatformDsl { actual fun coroutineCall(lambda: suspend () -> T): CoroutineCall = JvmCoroutineCall(lambda) - actual fun unboxClass(cls: KClass<*>): KClass<*> = cls.boxedClass() + actual fun unboxClass(cls: KClass<*>): KClass<*> = cls.boxedClass @Suppress("UNCHECKED_CAST") actual fun boxCast( diff --git a/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt b/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt index 7aa2b90b6..650c7aeca 100644 --- a/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -3,7 +3,6 @@ package io.mockk import kotlin.reflect.KClass import kotlin.reflect.KProperty1 import kotlin.reflect.full.declaredMemberProperties -import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.isAccessible /** @@ -15,71 +14,58 @@ import kotlin.reflect.jvm.isAccessible */ internal object ValueClassSupportDsl { - private val valueClassFieldCache = mutableMapOf, KProperty1>() - /** - * Get boxed value of any value class + * Underlying property value of a **`value class`** or self. * - * @return boxed value of value class, if this is value class, else just itself + * The type of the return might also be a `value class`! */ - fun T.boxedValue(): Any? { - if (!this::class.isValueClass()) return this - - // get backing field - val backingField = this::class.valueField() - - // get boxed value + val T.boxedValue: Any? @Suppress("UNCHECKED_CAST") - return (backingField as KProperty1).get(this) - } + get() = if (!this::class.isValue_safe) { + this + } else { + (this::class as KClass).boxedProperty.get(this) + } /** - * Get class of boxed value of any value class + * Underlying property class of a **`value class`** or self. * - * @return class of boxed value, if this is value class, else just class of itself + * The returned class might also be a `value class`! */ - fun T.boxedClass(): KClass<*> { - return this::class.boxedClass() - } + val KClass<*>.boxedClass: KClass<*> + get() = if (!this.isValue_safe) { + this + } else { + this.boxedProperty.returnType.classifier as KClass<*> + } + + private val valueClassFieldCache = mutableMapOf, KProperty1>() /** - * Get the KClass of boxed value if this is a value class. + * Underlying property of a **`value class`**. * - * @return class of boxed value, if this is value class, else just class of itself + * The underlying property might also be a `value class`! */ - fun KClass<*>.boxedClass(): KClass<*> { - if (!this.isValueClass()) return this - - // get backing field - val backingField = this.valueField() - - // get boxed value - return backingField.returnType.classifier as KClass<*> - } - - - private fun KClass.valueField(): KProperty1 { - @Suppress("UNCHECKED_CAST") - return valueClassFieldCache.getOrPut(this) { - require(isValue) { "$this is not a value class" } - - // value classes always have a primary constructor... - val constructor = primaryConstructor!! - // ...and exactly one constructor parameter - val constructorParameter = constructor.parameters.first() - // ...with a backing field - val backingField = declaredMemberProperties - .first { it.name == constructorParameter.name } - .apply { isAccessible = true } - - backingField - } as KProperty1 - } - - private fun KClass.isValueClass() = try { - this.isValue - } catch (_: Throwable) { - false - } + private val KClass.boxedProperty: KProperty1 + get() = if (!this.isValue_safe) { + throw UnsupportedOperationException("$this is not a value class") + } else { + // value classes always have exactly one property + @Suppress("UNCHECKED_CAST") + valueClassFieldCache.getOrPut(this) { + this.declaredMemberProperties.first().apply { isAccessible = true } + } as KProperty1 + } + /** + * Returns `true` if calling [KClass.isValue] is safe. + * + * (In some instances [KClass.isValue] can throw an exception.) + */ + private val KClass.isValue_safe: Boolean + get() = try { + this.isValue + } catch (_: UnsupportedOperationException) { + false + } } diff --git a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt index a0681b77d..0816b89c6 100644 --- a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt +++ b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt @@ -5,128 +5,490 @@ import io.mockk.mockk import io.mockk.slot import io.mockk.verify import kotlin.jvm.JvmInline +import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals class ValueClassTest { - private val mock = mockk() + private val dummyValueWrapperArg get() = DummyValueWrapper(DummyValue(42)) + private val dummyValueWrapperReturn get() = DummyValueWrapper(DummyValue(99)) + + private val dummyValueInnerArg get() = DummyValue(101) + private val dummyValueInnerReturn get() = DummyValue(202) + + // + @Test + fun `arg is ValueClass, returns Wrapper`() { + val mock = mockk { + every { argValueClassReturnWrapper(dummyValueInnerArg) } returns dummyValueWrapperReturn + } + + assertEquals(dummyValueWrapperReturn, mock.argValueClassReturnWrapper(dummyValueInnerArg)) + + verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + } + + @Test + fun `arg is any(ValueClass), returns Wrapper`() { + val mock = mockk { + every { argValueClassReturnWrapper(any()) } returns dummyValueWrapperReturn + } + + assertEquals(dummyValueWrapperArg, mock.argValueClassReturnWrapper(dummyValueInnerArg)) + + verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + } + + @Test + fun `arg is slot(ValueClass), returns Wrapper`() { + val slot = slot() + + val mock = mockk { + every { argValueClassReturnWrapper(capture(slot)) } returns dummyValueWrapperReturn + } + + val result = mock.argValueClassReturnWrapper(dummyValueInnerArg) + + assertEquals(dummyValueWrapperReturn, result) + + assertEquals(dummyValueInnerArg, slot.captured) + + verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + } + + @Test + fun `arg is ValueClass, answers Wrapper`() { + val mock = mockk { + every { argValueClassReturnWrapper(dummyValueInnerArg) } answers { dummyValueWrapperReturn } + } + + assertEquals(dummyValueWrapperReturn, mock.argValueClassReturnWrapper(dummyValueInnerArg)) + + verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + } + + @Test + fun `arg is any(ValueClass), answers Wrapper`() { + val mock = mockk { + every { argValueClassReturnWrapper(any()) } answers { dummyValueWrapperReturn } + } + + assertEquals(dummyValueWrapperReturn, mock.argValueClassReturnWrapper(dummyValueInnerArg)) + + verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + } + + @Test + fun `arg is slot(ValueClass), answers Wrapper`() { + val slot = slot() + + val mock = mockk { + every { argValueClassReturnWrapper(capture(slot)) } answers { dummyValueWrapperReturn } + } + + val result = mock.argValueClassReturnWrapper(dummyValueInnerArg) + + assertEquals(dummyValueWrapperReturn, result) + + assertEquals(dummyValueInnerArg, slot.captured) + + verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + } + // + + // + @Test + fun `arg is ValueClass, returns Inner`() { + val mock = mockk { + every { argValueClassReturnValueClass(dummyValueInnerArg) } returns dummyValueInnerReturn + } + + assertEquals(dummyValueInnerReturn, mock.argValueClassReturnValueClass(dummyValueInnerArg)) + + verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + } + + @Test + fun `arg is any(ValueClass), returns Inner`() { + val mock = mockk { + every { argValueClassReturnValueClass(any()) } returns dummyValueInnerReturn + } + + assertEquals(dummyValueInnerReturn, mock.argValueClassReturnValueClass(dummyValueInnerArg)) + + verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + } + + @Test + fun `arg is slot(ValueClass), returns Inner`() { + val slot = slot() + val mock = mockk { + every { argValueClassReturnValueClass(capture(slot)) } returns dummyValueInnerReturn + } + + val result = mock.argValueClassReturnValueClass(dummyValueInnerArg) + + assertEquals(dummyValueInnerReturn, result) + + assertEquals(dummyValueInnerArg, slot.captured) + + verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + } + + @Test + fun `arg is ValueClass, answers Inner`() { + val mock = mockk { + every { argValueClassReturnValueClass(dummyValueInnerArg) } answers { dummyValueInnerReturn } + } + + assertEquals(dummyValueInnerReturn, mock.argValueClassReturnValueClass(dummyValueInnerArg)) + + verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + } + + @Test + fun `arg is any(ValueClass), answers Inner`() { + val mock = mockk { + every { argValueClassReturnValueClass(any()) } answers { dummyValueInnerReturn } + } + + assertEquals(dummyValueInnerReturn, mock.argValueClassReturnValueClass(dummyValueInnerArg)) + + verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + } + + @Test + fun `arg is slot(ValueClass), answers Inner`() { + val slot = slot() + + val mock = mockk { + every { argValueClassReturnValueClass(capture(slot)) } answers { dummyValueInnerReturn } + } + + val result = mock.argValueClassReturnValueClass(dummyValueInnerArg) + + assertEquals(dummyValueInnerReturn, result) + + assertEquals(dummyValueInnerArg, slot.captured) + + verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + } + // + + // + @Test + fun `arg is Outer, returns Inner`() { + val mock = mockk { + every { argWrapperReturnValueClass(dummyValueWrapperArg) } returns dummyValueInnerReturn + } + + assertEquals(dummyValueInnerReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) + + verify { mock.argWrapperReturnValueClass(dummyValueWrapperArg) } + } + + @Test + fun `arg is any(Outer), returns Inner`() { + val mock = mockk { + every { argWrapperReturnValueClass(any()) } returns dummyValueInnerReturn + } + + assertEquals(dummyValueInnerReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) + + verify { mock.argWrapperReturnValueClass(dummyValueWrapperArg) } + } @Test - fun valueClassObjectAsReturnValue() { - every { mock.requestValue() } returns DummyValue(42) + fun `arg is slot(Outer), returns Inner`() { + val slot = slot() + val mock = mockk { + every { argWrapperReturnValueClass(capture(slot)) } returns dummyValueInnerReturn + } + + val result = mock.argWrapperReturnValueClass(dummyValueWrapperArg) - assertEquals(DummyValue(42), mock.requestValue()) + assertEquals(dummyValueInnerReturn, result) - verify { mock.requestValue() } + assertEquals(dummyValueWrapperArg, slot.captured) + + verify { mock.argWrapperReturnValueClass(dummyValueWrapperArg) } } @Test - fun valueClassObjectAsFunctionArgumentAndReturnValue() { - every { mock.processValue(DummyValue(1)) } returns DummyValue(42) + fun `arg is Outer, answers Inner`() { + val mock = mockk { + every { argWrapperReturnValueClass(dummyValueWrapperArg) } answers { dummyValueInnerReturn } + } - assertEquals(DummyValue(42), mock.processValue(DummyValue(1))) + assertEquals(dummyValueInnerReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) - verify { mock.processValue(DummyValue(1)) } + verify { mock.argWrapperReturnValueClass(dummyValueWrapperArg) } } @Test - fun valueClassObjectAsFunctionArgumentAndAnswerValue() { - every { mock.processValue(DummyValue(1)) } answers { DummyValue(42) } + fun `arg is any(Outer), answers Inner`() { + val mock = mockk { + every { argWrapperReturnValueClass(any()) } answers { dummyValueInnerReturn } + } - assertEquals(DummyValue(42), mock.processValue(DummyValue(1))) + assertEquals(dummyValueInnerReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) - verify { mock.processValue(DummyValue(1)) } + verify { mock.argWrapperReturnValueClass(dummyValueWrapperArg) } } @Test - fun anyValueClassMatcherAsFunctionArgumentAndValueClassObjectAsReturnValue() { - every { mock.processValue(any()) } returns DummyValue(42) + fun `arg is slot(Outer), answers Inner`() { + val slot = slot() - assertEquals(DummyValue(42), mock.processValue(DummyValue(1))) + val mock = mockk { + every { argWrapperReturnValueClass(capture(slot)) } answers { dummyValueInnerReturn } + } - verify { mock.processValue(DummyValue(1)) } + val result = mock.argWrapperReturnValueClass(dummyValueWrapperArg) + + assertEquals(dummyValueInnerReturn, result) + + assertEquals(dummyValueWrapperArg, slot.captured) + + verify { mock.argWrapperReturnValueClass(dummyValueWrapperArg) } } + // + // @Test - fun `any matcher for value class`() { - val mock = mockk(relaxed = true) - val givenResult = 1 - every { mock.doSomething(any()) } returns givenResult + fun `arg is Outer, returns Wrapper`() { + val mock = mockk { + every { argWrapperReturnWrapper(dummyValueWrapperArg) } returns dummyValueWrapperReturn + } - val result = mock.doSomething(ValueDummy("moin")) + assertEquals(dummyValueWrapperReturn, mock.argWrapperReturnWrapper(dummyValueWrapperArg)) - assertEquals(givenResult, result) + verify { mock.argWrapperReturnWrapper(dummyValueWrapperArg) } } @Test - fun `slot for value class`() { - val mock = mockk(relaxed = true) - val slot = slot() - val givenResult = 1 - every { mock.doSomething(capture(slot)) } returns givenResult + fun `arg is any(Outer), returns Wrapper`() { + val mock = mockk { + every { argWrapperReturnWrapper(any()) } returns dummyValueWrapperReturn + } - val givenParameter = ValueDummy("s") + assertEquals(dummyValueWrapperReturn, mock.argWrapperReturnWrapper(dummyValueWrapperArg)) + + verify { mock.argWrapperReturnWrapper(dummyValueWrapperArg) } + } + + @Test + fun `arg is slot(Outer), returns Wrapper`() { + val slot = slot() + val mock = mockk { + every { argWrapperReturnWrapper(capture(slot)) } returns dummyValueWrapperReturn + } - val result = mock.doSomething(givenParameter) + val result = mock.argWrapperReturnWrapper(dummyValueWrapperArg) - assertEquals(givenResult, result) - assertEquals(givenParameter, slot.captured) + assertEquals(dummyValueWrapperReturn, result) + + assertEquals(dummyValueWrapperArg, slot.captured) + + verify { mock.argWrapperReturnWrapper(dummyValueWrapperArg) } + } + + @Test + fun `arg is Outer, answers Wrapper`() { + val mock = mockk { + every { argWrapperReturnWrapper(dummyValueWrapperArg) } answers { dummyValueWrapperReturn } + } + + assertEquals(dummyValueWrapperReturn, mock.argWrapperReturnWrapper(dummyValueWrapperArg)) + + verify { mock.argWrapperReturnWrapper(dummyValueWrapperArg) } + } + + @Test + fun `arg is any(Outer), answers Wrapper`() { + val mock = mockk { + every { argWrapperReturnWrapper(any()) } answers { dummyValueWrapperReturn } + } + + assertEquals(dummyValueWrapperReturn, mock.argWrapperReturnWrapper(dummyValueWrapperArg)) + + verify { mock.argWrapperReturnWrapper(dummyValueWrapperArg) } } @Test - fun `value class as return value`() { - val mock = mockk(relaxed = true) - val givenResult = ValueDummy("moin") - every { mock.getSomething() } returns givenResult + fun `arg is slot(Outer), answers Wrapper`() { + val slot = slot() + + val mock = mockk { + every { argWrapperReturnWrapper(capture(slot)) } answers { dummyValueWrapperReturn } + } + + val result = mock.argWrapperReturnWrapper(dummyValueWrapperArg) - val result = mock.getSomething() + assertEquals(dummyValueWrapperReturn, result) - assertEquals(givenResult, result) + assertEquals(dummyValueWrapperArg, slot.captured) + + verify { mock.argWrapperReturnWrapper(dummyValueWrapperArg) } } + // + // /** https://github.com/mockk/mockk/issues/729 */ @Test - fun `verify function with UInt return can be stubbed`() { + fun `arg None, returns UInt`() { val mock = mockk { - every { getUInt() } returns 999u + every { argNoneReturnsUInt() } returns 999u } - val result = mock.getUInt() + val result = mock.argNoneReturnsUInt() assertEquals(999u, result) } /** https://github.com/mockk/mockk/issues/729 */ @Test - fun `verify extension function with UInt return can be stubbed`() { + fun `arg None, answers UInt`() { + val mock = mockk { + every { argNoneReturnsUInt() } answers { 999u } + } + + val result = mock.argNoneReturnsUInt() + + assertEquals(999u, result) + } + // + + // + // + @Test + @Ignore // TODO fix infinite loop + fun `receiver is String, return is Inner`() { + val fn = mockk DummyValue>() + + every { "string".fn() } returns dummyValueInnerReturn + + val result = "string".fn() + + assertEquals(dummyValueInnerReturn, result) + } - val fn = mockk UInt>() + @Test + @Ignore // TODO fix infinite loop + fun `receiver is String, return is Wrapper`() { + val fn = mockk DummyValueWrapper>() - every { "string".fn() } returns 777u + every { "string".fn() } returns dummyValueWrapperArg val result = "string".fn() - assertEquals(777u, result) + assertEquals(dummyValueWrapperArg, result) } -} + // -@JvmInline -value class DummyValue(val value: Int) + // + @Test + @Ignore // TODO fix infinite loop + fun `receiver is Outer, return is Wrapper`() { + val fn = mockk DummyValueWrapper>() -class DummyService { + every { dummyValueWrapperArg.fn() } returns dummyValueWrapperArg - fun requestValue() = DummyValue(0) + val result = dummyValueWrapperArg.fn() - fun processValue(value: DummyValue) = DummyValue(0) + assertEquals(dummyValueWrapperArg, result) + } - fun getUInt(): UInt = 123u -} + @Test + @Ignore // TODO fix infinite loop + fun `receiver is Outer, return is Inner`() { + val fn = mockk DummyValue>() + + every { dummyValueWrapperArg.fn() } returns dummyValueInnerReturn + + val result = dummyValueWrapperArg.fn() + + assertEquals(dummyValueInnerArg, result) + } + + @Test + @Ignore // TODO fix infinite loop + fun `receiver is Outer, return is String`() { + val fn = mockk String>() + + every { dummyValueWrapperArg.fn() } returns "example" + + val result = dummyValueWrapperArg.fn() + + assertEquals("example", result) + } + // + + // + @Test + @Ignore // TODO fix infinite loop + fun `receiver is Inner, return is Wrapper`() { + val fn = mockk DummyValueWrapper>() + + every { dummyValueInnerArg.fn() } returns dummyValueWrapperReturn -@JvmInline -value class ValueDummy(val value: String) + val result = dummyValueInnerArg.fn() -interface ValueServiceDummy { - fun doSomething(value: ValueDummy): Int - fun getSomething(): ValueDummy + assertEquals(dummyValueWrapperArg, result) + } + + @Test + @Ignore // TODO fix infinite loop + fun `receiver is Inner, return is Inner`() { + val fn = mockk DummyValue>() + + every { dummyValueInnerArg.fn() } returns dummyValueInnerReturn + + val result = dummyValueInnerArg.fn() + + assertEquals(dummyValueInnerReturn, result) + } + + @Test + @Ignore // TODO fix infinite loop + fun `receiver is Inner, return is String`() { + val fn = mockk String>() + + every { dummyValueInnerArg.fn() } returns "example" + + val result = dummyValueInnerArg.fn() + + assertEquals("example", result) + } + // + // + + + companion object { + + @JvmInline + value class DummyValue(val value: Int) + + @JvmInline + value class DummyValueWrapper(val value: DummyValue) + + class DummyService { + + fun argWrapperReturnWrapper(outer: DummyValueWrapper): DummyValueWrapper = + DummyValueWrapper(DummyValue(0)) + + fun argWrapperReturnValueClass(outer: DummyValueWrapper): DummyValue = + DummyValue(0) + + fun argValueClassReturnWrapper(inner: DummyValue): DummyValueWrapper = + DummyValueWrapper(inner) + + fun argValueClassReturnValueClass(inner: DummyValue): DummyValue = + DummyValue(0) + + + fun argNoneReturnsUInt(): UInt = 123u + } + } } diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt index cb586d936..4b2dc598a 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/InternalPlatform.kt @@ -63,7 +63,7 @@ actual object InternalPlatform { actual fun packRef(arg: Any?): Any? { return when { arg == null -> null - isPassedByValue(arg.boxedClass()) -> arg.boxedValue() + isPassedByValue(arg::class.boxedClass) -> arg.boxedValue else -> ref(arg) } } diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt index 71552c367..b08b9250d 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt @@ -151,7 +151,7 @@ object JvmMockFactoryHelper { is KType -> kotlinReturnType.classifier as? KClass<*> ?: returnType.kotlin is KClass<*> -> kotlinReturnType else -> returnType.kotlin - }.boxedClass() + }.boxedClass val androidCompatibleReturnType = if (returnType.qualifiedName in androidUnsupportedTypes) { this@toDescription.returnType.kotlin From af349e7c138dede61bcf6fe9901f69c1e9e5c28a Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 24 Jul 2022 10:57:59 +0200 Subject: [PATCH 24/36] disable wrapped value class tests, rename tests for consistency --- .../test/kotlin/io/mockk/it/ValueClassTest.kt | 239 ++++++++++-------- 1 file changed, 128 insertions(+), 111 deletions(-) diff --git a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt index 0816b89c6..826a0e7b1 100644 --- a/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt +++ b/mockk/common/src/test/kotlin/io/mockk/it/ValueClassTest.kt @@ -14,201 +14,210 @@ class ValueClassTest { private val dummyValueWrapperArg get() = DummyValueWrapper(DummyValue(42)) private val dummyValueWrapperReturn get() = DummyValueWrapper(DummyValue(99)) - private val dummyValueInnerArg get() = DummyValue(101) - private val dummyValueInnerReturn get() = DummyValue(202) + private val dummyValueClassArg get() = DummyValue(101) + private val dummyValueClassReturn get() = DummyValue(202) - // + // @Test - fun `arg is ValueClass, returns Wrapper`() { + fun `arg is ValueClass, returns ValueClass`() { val mock = mockk { - every { argValueClassReturnWrapper(dummyValueInnerArg) } returns dummyValueWrapperReturn + every { argValueClassReturnValueClass(dummyValueClassArg) } returns dummyValueClassReturn } - assertEquals(dummyValueWrapperReturn, mock.argValueClassReturnWrapper(dummyValueInnerArg)) + assertEquals(dummyValueClassReturn, mock.argValueClassReturnValueClass(dummyValueClassArg)) - verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + verify { mock.argValueClassReturnValueClass(dummyValueClassArg) } } @Test - fun `arg is any(ValueClass), returns Wrapper`() { + fun `arg is any(ValueClass), returns ValueClass`() { val mock = mockk { - every { argValueClassReturnWrapper(any()) } returns dummyValueWrapperReturn + every { argValueClassReturnValueClass(any()) } returns dummyValueClassReturn } - assertEquals(dummyValueWrapperArg, mock.argValueClassReturnWrapper(dummyValueInnerArg)) + assertEquals(dummyValueClassReturn, mock.argValueClassReturnValueClass(dummyValueClassArg)) - verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + verify { mock.argValueClassReturnValueClass(dummyValueClassArg) } } @Test - fun `arg is slot(ValueClass), returns Wrapper`() { + fun `arg is slot(ValueClass), returns ValueClass`() { val slot = slot() - val mock = mockk { - every { argValueClassReturnWrapper(capture(slot)) } returns dummyValueWrapperReturn + every { argValueClassReturnValueClass(capture(slot)) } returns dummyValueClassReturn } - val result = mock.argValueClassReturnWrapper(dummyValueInnerArg) + val result = mock.argValueClassReturnValueClass(dummyValueClassArg) - assertEquals(dummyValueWrapperReturn, result) + assertEquals(dummyValueClassReturn, result) - assertEquals(dummyValueInnerArg, slot.captured) + assertEquals(dummyValueClassArg, slot.captured) - verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + verify { mock.argValueClassReturnValueClass(dummyValueClassArg) } } @Test - fun `arg is ValueClass, answers Wrapper`() { + fun `arg is ValueClass, answers ValueClass`() { val mock = mockk { - every { argValueClassReturnWrapper(dummyValueInnerArg) } answers { dummyValueWrapperReturn } + every { argValueClassReturnValueClass(dummyValueClassArg) } answers { dummyValueClassReturn } } - assertEquals(dummyValueWrapperReturn, mock.argValueClassReturnWrapper(dummyValueInnerArg)) + assertEquals(dummyValueClassReturn, mock.argValueClassReturnValueClass(dummyValueClassArg)) - verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + verify { mock.argValueClassReturnValueClass(dummyValueClassArg) } } @Test - fun `arg is any(ValueClass), answers Wrapper`() { + fun `arg is any(ValueClass), answers ValueClass`() { val mock = mockk { - every { argValueClassReturnWrapper(any()) } answers { dummyValueWrapperReturn } + every { argValueClassReturnValueClass(any()) } answers { dummyValueClassReturn } } - assertEquals(dummyValueWrapperReturn, mock.argValueClassReturnWrapper(dummyValueInnerArg)) + assertEquals(dummyValueClassReturn, mock.argValueClassReturnValueClass(dummyValueClassArg)) - verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + verify { mock.argValueClassReturnValueClass(dummyValueClassArg) } } @Test - fun `arg is slot(ValueClass), answers Wrapper`() { + fun `arg is slot(ValueClass), answers ValueClass`() { val slot = slot() val mock = mockk { - every { argValueClassReturnWrapper(capture(slot)) } answers { dummyValueWrapperReturn } + every { argValueClassReturnValueClass(capture(slot)) } answers { dummyValueClassReturn } } - val result = mock.argValueClassReturnWrapper(dummyValueInnerArg) + val result = mock.argValueClassReturnValueClass(dummyValueClassArg) - assertEquals(dummyValueWrapperReturn, result) + assertEquals(dummyValueClassReturn, result) - assertEquals(dummyValueInnerArg, slot.captured) + assertEquals(dummyValueClassArg, slot.captured) - verify { mock.argValueClassReturnWrapper(dummyValueInnerArg) } + verify { mock.argValueClassReturnValueClass(dummyValueClassArg) } } // - // + // @Test - fun `arg is ValueClass, returns Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is ValueClass, returns Wrapper`() { val mock = mockk { - every { argValueClassReturnValueClass(dummyValueInnerArg) } returns dummyValueInnerReturn + every { argValueClassReturnWrapper(dummyValueClassArg) } returns dummyValueWrapperReturn } - assertEquals(dummyValueInnerReturn, mock.argValueClassReturnValueClass(dummyValueInnerArg)) + assertEquals(dummyValueWrapperReturn, mock.argValueClassReturnWrapper(dummyValueClassArg)) - verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + verify { mock.argValueClassReturnWrapper(dummyValueClassArg) } } @Test - fun `arg is any(ValueClass), returns Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is any(ValueClass), returns Wrapper`() { val mock = mockk { - every { argValueClassReturnValueClass(any()) } returns dummyValueInnerReturn + every { argValueClassReturnWrapper(any()) } returns dummyValueWrapperReturn } - assertEquals(dummyValueInnerReturn, mock.argValueClassReturnValueClass(dummyValueInnerArg)) + assertEquals(dummyValueWrapperArg, mock.argValueClassReturnWrapper(dummyValueClassArg)) - verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + verify { mock.argValueClassReturnWrapper(dummyValueClassArg) } } @Test - fun `arg is slot(ValueClass), returns Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is slot(ValueClass), returns Wrapper`() { val slot = slot() + val mock = mockk { - every { argValueClassReturnValueClass(capture(slot)) } returns dummyValueInnerReturn + every { argValueClassReturnWrapper(capture(slot)) } returns dummyValueWrapperReturn } - val result = mock.argValueClassReturnValueClass(dummyValueInnerArg) + val result = mock.argValueClassReturnWrapper(dummyValueClassArg) - assertEquals(dummyValueInnerReturn, result) + assertEquals(dummyValueWrapperReturn, result) - assertEquals(dummyValueInnerArg, slot.captured) + assertEquals(dummyValueClassArg, slot.captured) - verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + verify { mock.argValueClassReturnWrapper(dummyValueClassArg) } } @Test - fun `arg is ValueClass, answers Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is ValueClass, answers Wrapper`() { val mock = mockk { - every { argValueClassReturnValueClass(dummyValueInnerArg) } answers { dummyValueInnerReturn } + every { argValueClassReturnWrapper(dummyValueClassArg) } answers { dummyValueWrapperReturn } } - assertEquals(dummyValueInnerReturn, mock.argValueClassReturnValueClass(dummyValueInnerArg)) + assertEquals(dummyValueWrapperReturn, mock.argValueClassReturnWrapper(dummyValueClassArg)) - verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + verify { mock.argValueClassReturnWrapper(dummyValueClassArg) } } @Test - fun `arg is any(ValueClass), answers Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is any(ValueClass), answers Wrapper`() { val mock = mockk { - every { argValueClassReturnValueClass(any()) } answers { dummyValueInnerReturn } + every { argValueClassReturnWrapper(any()) } answers { dummyValueWrapperReturn } } - assertEquals(dummyValueInnerReturn, mock.argValueClassReturnValueClass(dummyValueInnerArg)) + assertEquals(dummyValueWrapperReturn, mock.argValueClassReturnWrapper(dummyValueClassArg)) - verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + verify { mock.argValueClassReturnWrapper(dummyValueClassArg) } } @Test - fun `arg is slot(ValueClass), answers Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is slot(ValueClass), answers Wrapper`() { val slot = slot() val mock = mockk { - every { argValueClassReturnValueClass(capture(slot)) } answers { dummyValueInnerReturn } + every { argValueClassReturnWrapper(capture(slot)) } answers { dummyValueWrapperReturn } } - val result = mock.argValueClassReturnValueClass(dummyValueInnerArg) + val result = mock.argValueClassReturnWrapper(dummyValueClassArg) - assertEquals(dummyValueInnerReturn, result) + assertEquals(dummyValueWrapperReturn, result) - assertEquals(dummyValueInnerArg, slot.captured) + assertEquals(dummyValueClassArg, slot.captured) - verify { mock.argValueClassReturnValueClass(dummyValueInnerArg) } + verify { mock.argValueClassReturnWrapper(dummyValueClassArg) } } // // @Test - fun `arg is Outer, returns Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is Wrapper, returns ValueClass`() { val mock = mockk { - every { argWrapperReturnValueClass(dummyValueWrapperArg) } returns dummyValueInnerReturn + every { argWrapperReturnValueClass(dummyValueWrapperArg) } returns dummyValueClassReturn } - assertEquals(dummyValueInnerReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) + assertEquals(dummyValueClassReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) verify { mock.argWrapperReturnValueClass(dummyValueWrapperArg) } } @Test - fun `arg is any(Outer), returns Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is any(Wrapper), returns ValueClass`() { val mock = mockk { - every { argWrapperReturnValueClass(any()) } returns dummyValueInnerReturn + every { argWrapperReturnValueClass(any()) } returns dummyValueClassReturn } - assertEquals(dummyValueInnerReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) + assertEquals(dummyValueClassReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) verify { mock.argWrapperReturnValueClass(dummyValueWrapperArg) } } @Test - fun `arg is slot(Outer), returns Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is slot(Wrapper), returns ValueClass`() { val slot = slot() val mock = mockk { - every { argWrapperReturnValueClass(capture(slot)) } returns dummyValueInnerReturn + every { argWrapperReturnValueClass(capture(slot)) } returns dummyValueClassReturn } val result = mock.argWrapperReturnValueClass(dummyValueWrapperArg) - assertEquals(dummyValueInnerReturn, result) + assertEquals(dummyValueClassReturn, result) assertEquals(dummyValueWrapperArg, slot.captured) @@ -216,38 +225,41 @@ class ValueClassTest { } @Test - fun `arg is Outer, answers Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is Wrapper, answers ValueClass`() { val mock = mockk { - every { argWrapperReturnValueClass(dummyValueWrapperArg) } answers { dummyValueInnerReturn } + every { argWrapperReturnValueClass(dummyValueWrapperArg) } answers { dummyValueClassReturn } } - assertEquals(dummyValueInnerReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) + assertEquals(dummyValueClassReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) verify { mock.argWrapperReturnValueClass(dummyValueWrapperArg) } } @Test - fun `arg is any(Outer), answers Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is any(Wrapper), answers ValueClass`() { val mock = mockk { - every { argWrapperReturnValueClass(any()) } answers { dummyValueInnerReturn } + every { argWrapperReturnValueClass(any()) } answers { dummyValueClassReturn } } - assertEquals(dummyValueInnerReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) + assertEquals(dummyValueClassReturn, mock.argWrapperReturnValueClass(dummyValueWrapperArg)) verify { mock.argWrapperReturnValueClass(dummyValueWrapperArg) } } @Test - fun `arg is slot(Outer), answers Inner`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is slot(Wrapper), answers ValueClass`() { val slot = slot() val mock = mockk { - every { argWrapperReturnValueClass(capture(slot)) } answers { dummyValueInnerReturn } + every { argWrapperReturnValueClass(capture(slot)) } answers { dummyValueClassReturn } } val result = mock.argWrapperReturnValueClass(dummyValueWrapperArg) - assertEquals(dummyValueInnerReturn, result) + assertEquals(dummyValueClassReturn, result) assertEquals(dummyValueWrapperArg, slot.captured) @@ -257,7 +269,8 @@ class ValueClassTest { // @Test - fun `arg is Outer, returns Wrapper`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is Wrapper, returns Wrapper`() { val mock = mockk { every { argWrapperReturnWrapper(dummyValueWrapperArg) } returns dummyValueWrapperReturn } @@ -268,7 +281,8 @@ class ValueClassTest { } @Test - fun `arg is any(Outer), returns Wrapper`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is any(Wrapper), returns Wrapper`() { val mock = mockk { every { argWrapperReturnWrapper(any()) } returns dummyValueWrapperReturn } @@ -279,7 +293,8 @@ class ValueClassTest { } @Test - fun `arg is slot(Outer), returns Wrapper`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is slot(Wrapper), returns Wrapper`() { val slot = slot() val mock = mockk { every { argWrapperReturnWrapper(capture(slot)) } returns dummyValueWrapperReturn @@ -295,7 +310,8 @@ class ValueClassTest { } @Test - fun `arg is Outer, answers Wrapper`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is Wrapper, answers Wrapper`() { val mock = mockk { every { argWrapperReturnWrapper(dummyValueWrapperArg) } answers { dummyValueWrapperReturn } } @@ -306,7 +322,8 @@ class ValueClassTest { } @Test - fun `arg is any(Outer), answers Wrapper`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is any(Wrapper), answers Wrapper`() { val mock = mockk { every { argWrapperReturnWrapper(any()) } answers { dummyValueWrapperReturn } } @@ -317,7 +334,8 @@ class ValueClassTest { } @Test - fun `arg is slot(Outer), answers Wrapper`() { + @Ignore // TODO support nested value classes https://github.com/mockk/mockk/issues/859 + fun `arg is slot(Wrapper), answers Wrapper`() { val slot = slot() val mock = mockk { @@ -364,14 +382,14 @@ class ValueClassTest { // @Test @Ignore // TODO fix infinite loop - fun `receiver is String, return is Inner`() { + fun `receiver is String, return is ValueClass`() { val fn = mockk DummyValue>() - every { "string".fn() } returns dummyValueInnerReturn + every { "string".fn() } returns dummyValueClassReturn val result = "string".fn() - assertEquals(dummyValueInnerReturn, result) + assertEquals(dummyValueClassReturn, result) } @Test @@ -387,10 +405,10 @@ class ValueClassTest { } // - // + // @Test @Ignore // TODO fix infinite loop - fun `receiver is Outer, return is Wrapper`() { + fun `receiver is Wrapper, return is Wrapper`() { val fn = mockk DummyValueWrapper>() every { dummyValueWrapperArg.fn() } returns dummyValueWrapperArg @@ -402,19 +420,19 @@ class ValueClassTest { @Test @Ignore // TODO fix infinite loop - fun `receiver is Outer, return is Inner`() { + fun `receiver is Wrapper, return is ValueClass`() { val fn = mockk DummyValue>() - every { dummyValueWrapperArg.fn() } returns dummyValueInnerReturn + every { dummyValueWrapperArg.fn() } returns dummyValueClassReturn val result = dummyValueWrapperArg.fn() - assertEquals(dummyValueInnerArg, result) + assertEquals(dummyValueClassArg, result) } @Test @Ignore // TODO fix infinite loop - fun `receiver is Outer, return is String`() { + fun `receiver is Wrapper, return is String`() { val fn = mockk String>() every { dummyValueWrapperArg.fn() } returns "example" @@ -425,46 +443,45 @@ class ValueClassTest { } // - // + // @Test @Ignore // TODO fix infinite loop - fun `receiver is Inner, return is Wrapper`() { + fun `receiver is ValueClass, return is Wrapper`() { val fn = mockk DummyValueWrapper>() - every { dummyValueInnerArg.fn() } returns dummyValueWrapperReturn + every { dummyValueClassArg.fn() } returns dummyValueWrapperReturn - val result = dummyValueInnerArg.fn() + val result = dummyValueClassArg.fn() assertEquals(dummyValueWrapperArg, result) } @Test @Ignore // TODO fix infinite loop - fun `receiver is Inner, return is Inner`() { + fun `receiver is ValueClass, return is ValueClass`() { val fn = mockk DummyValue>() - every { dummyValueInnerArg.fn() } returns dummyValueInnerReturn + every { dummyValueClassArg.fn() } returns dummyValueClassReturn - val result = dummyValueInnerArg.fn() + val result = dummyValueClassArg.fn() - assertEquals(dummyValueInnerReturn, result) + assertEquals(dummyValueClassReturn, result) } @Test @Ignore // TODO fix infinite loop - fun `receiver is Inner, return is String`() { + fun `receiver is ValueClass, return is String`() { val fn = mockk String>() - every { dummyValueInnerArg.fn() } returns "example" + every { dummyValueClassArg.fn() } returns "example" - val result = dummyValueInnerArg.fn() + val result = dummyValueClassArg.fn() assertEquals("example", result) } // // - companion object { @JvmInline @@ -475,16 +492,16 @@ class ValueClassTest { class DummyService { - fun argWrapperReturnWrapper(outer: DummyValueWrapper): DummyValueWrapper = + fun argWrapperReturnWrapper(wrapper: DummyValueWrapper): DummyValueWrapper = DummyValueWrapper(DummyValue(0)) - fun argWrapperReturnValueClass(outer: DummyValueWrapper): DummyValue = + fun argWrapperReturnValueClass(wrapper: DummyValueWrapper): DummyValue = DummyValue(0) - fun argValueClassReturnWrapper(inner: DummyValue): DummyValueWrapper = - DummyValueWrapper(inner) + fun argValueClassReturnWrapper(valueClass: DummyValue): DummyValueWrapper = + DummyValueWrapper(valueClass) - fun argValueClassReturnValueClass(inner: DummyValue): DummyValue = + fun argValueClassReturnValueClass(valueClass: DummyValue): DummyValue = DummyValue(0) From 592bdaf710e1be674742afa61147dfad584d6072 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 24 Jul 2022 10:58:53 +0200 Subject: [PATCH 25/36] rename file to match obj name --- .../io/mockk/{ValueClassSupport.kt => ValueClassSupportDsl.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dsl/jvm/src/main/kotlin/io/mockk/{ValueClassSupport.kt => ValueClassSupportDsl.kt} (100%) diff --git a/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt b/dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupportDsl.kt similarity index 100% rename from dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt rename to dsl/jvm/src/main/kotlin/io/mockk/ValueClassSupportDsl.kt From fcaef2ea109d65cf0593182430206bada6ef47f8 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 24 Jul 2022 20:28:20 +0200 Subject: [PATCH 26/36] added timeout for Gradle test tasks --- build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index b8998b085..9085fd6b1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import java.time.Duration + buildscript { ext.kotlin_gradle_version = findProperty('kotlin.version')?.toString() ?: '1.6.0' ext.android_gradle_version = '7.0.0' @@ -44,4 +46,8 @@ subprojects { subProject -> languageVersion = "1.5" } } + + tasks.withType(Test).configureEach { + timeout.set(Duration.ofMinutes(2)) + } } From 8b8017cc8498ab8768cfccf05fc0d95efeaf1f9e Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 24 Jul 2022 20:35:36 +0200 Subject: [PATCH 27/36] bump test timeout --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9085fd6b1..8c0d28a46 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,6 @@ subprojects { subProject -> } tasks.withType(Test).configureEach { - timeout.set(Duration.ofMinutes(2)) + timeout.set(Duration.ofMinutes(5)) } } From 7e7ddd6f718a65fa3fc1ede78f8ac098bca58805 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 25 Jul 2022 19:04:41 +0200 Subject: [PATCH 28/36] add tests for #832 --- .../kotlin/io/mockk/it/SealedClassTest.kt | 49 +++++++++++++++++++ .../kotlin/io/mockk/it/SealedInterfaceTest.kt | 49 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 mockk/common/src/test/kotlin/io/mockk/it/SealedClassTest.kt create mode 100644 mockk/common/src/test/kotlin/io/mockk/it/SealedInterfaceTest.kt diff --git a/mockk/common/src/test/kotlin/io/mockk/it/SealedClassTest.kt b/mockk/common/src/test/kotlin/io/mockk/it/SealedClassTest.kt new file mode 100644 index 000000000..efa39cab2 --- /dev/null +++ b/mockk/common/src/test/kotlin/io/mockk/it/SealedClassTest.kt @@ -0,0 +1,49 @@ +package io.mockk.it + +import io.mockk.every +import io.mockk.mockk +import kotlin.test.Test +import kotlin.test.assertEquals + + +class SealedClassTest { + + @Test + fun serviceReturnsSealedClassImpl() { + val factory = mockk { + every { create() } returns Leaf(1) + } + + val result = factory.create() + + assertEquals(Leaf(1), result) + } + + @Test + fun serviceAnswersSealedClassImpl() { + val factory = mockk { + every { create() } answers { Leaf(1) } + } + + val result = factory.create() + + assertEquals(Leaf(1), result) + } + + companion object { + + sealed class Node + + data class Root(val id: Int) : Node() + data class Leaf(val id: Int) : Node() + + interface Factory { + fun create(): Node + } + + class FactoryImpl : Factory { + override fun create(): Node = Root(0) + } + + } +} diff --git a/mockk/common/src/test/kotlin/io/mockk/it/SealedInterfaceTest.kt b/mockk/common/src/test/kotlin/io/mockk/it/SealedInterfaceTest.kt new file mode 100644 index 000000000..91f727abf --- /dev/null +++ b/mockk/common/src/test/kotlin/io/mockk/it/SealedInterfaceTest.kt @@ -0,0 +1,49 @@ +package io.mockk.it + +import io.mockk.every +import io.mockk.mockk +import kotlin.test.Test +import kotlin.test.assertEquals + + +class SealedInterfaceTest { + + @Test + fun serviceReturnsSealedClassImpl() { + val factory = mockk { + every { create() } returns Leaf(1) + } + + val result = factory.create() + + assertEquals(Leaf(1), result) + } + + @Test + fun serviceAnswersSealedClassImpl() { + val factory = mockk { + every { create() } answers { Leaf(1) } + } + + val result = factory.create() + + assertEquals(Leaf(1), result) + } + + companion object { + + sealed interface Node + + data class Root(val id: Int) : Node + data class Leaf(val id: Int) : Node + + interface Factory { + fun create(): Node + } + + class FactoryImpl : Factory { + override fun create(): Node = Root(0) + } + + } +} From 73301d794c5ddb637c6f78ead714bae1fd54a0a5 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 25 Jul 2022 19:25:53 +0200 Subject: [PATCH 29/36] formatting --- .github/workflows/gradle.yml | 39 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5c3c90b35..27338317b 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -18,25 +18,25 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-${{ matrix.java-version }}-${{ matrix.kotlin-version }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.java-version }}-gradle- - - name: Set up JDK ${{ matrix.java-version }} - uses: actions/setup-java@v1 - with: - java-version: ${{ matrix.java-version }} - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Run tests with Gradle - uses: eskatos/gradle-command-action@v1 - with: - arguments: test --stacktrace -Pkotlin.version=${{ matrix.kotlin-version }} -Pkotlin.ir.enabled=${{ matrix.kotlin-ir-enabled }} + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-${{ matrix.java-version }}-${{ matrix.kotlin-version }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.java-version }}-gradle- + - name: Set up JDK ${{ matrix.java-version }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java-version }} + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Run tests with Gradle + uses: eskatos/gradle-command-action@v1 + with: + arguments: test --stacktrace -Pkotlin.version=${{ matrix.kotlin-version }} -Pkotlin.ir.enabled=${{ matrix.kotlin-ir-enabled }} android-instrumented-tests: runs-on: macos-latest strategy: @@ -61,4 +61,3 @@ jobs: with: api-level: ${{ matrix.api-level }} script: ./gradlew connectedCheck - From d04e042814b674cf3a60458124ae7c94634a743d Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 25 Jul 2022 19:26:05 +0200 Subject: [PATCH 30/36] add concurrency --- .github/workflows/gradle.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 27338317b..08247b0ac 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -17,6 +17,10 @@ jobs: fail-fast: false runs-on: ubuntu-latest + concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + steps: - uses: actions/checkout@v2 - uses: actions/cache@v2 From 98671f4dde13658e3d3e0c8b1e70192df13ce6af Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 25 Jul 2022 19:32:04 +0200 Subject: [PATCH 31/36] reduce number of JDK versions tested, bump Kotlin 1.6.0 to 1.6.21 --- .github/workflows/gradle.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 08247b0ac..c585c3ed7 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -10,9 +10,9 @@ jobs: tests: strategy: matrix: - java-version: [11, 12, 13, 14, 15, 16, 17] - kotlin-version: [1.5.31, 1.6.0, 1.7.10] - kotlin-ir-enabled: [true, false] + java-version: [ 11, 17, 18 ] # test LTS versions, and the newest + kotlin-version: [ 1.5.31, 1.6.21, 1.7.10 ] + kotlin-ir-enabled: [ true, false ] # in case one JDK fails, we still want to see results from others fail-fast: false runs-on: ubuntu-latest From fbd393a66fa1b685dd0a1182ffc6f30885ca711e Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 25 Jul 2022 19:36:37 +0200 Subject: [PATCH 32/36] move concurrency block --- .github/workflows/gradle.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index c585c3ed7..9e930783d 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -6,6 +6,10 @@ on: pull_request: branches: [ master ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: tests: strategy: @@ -16,11 +20,6 @@ jobs: # in case one JDK fails, we still want to see results from others fail-fast: false runs-on: ubuntu-latest - - concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - steps: - uses: actions/checkout@v2 - uses: actions/cache@v2 @@ -41,6 +40,7 @@ jobs: uses: eskatos/gradle-command-action@v1 with: arguments: test --stacktrace -Pkotlin.version=${{ matrix.kotlin-version }} -Pkotlin.ir.enabled=${{ matrix.kotlin-ir-enabled }} + android-instrumented-tests: runs-on: macos-latest strategy: From 2ce0e654aa6ac31fbd4633406b59cbfbb1e86238 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 25 Jul 2022 21:55:49 +0200 Subject: [PATCH 33/36] update to cachev3, separate Gradle caches, make hash key less strict --- .github/workflows/gradle.yml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 9e930783d..f7f31d7d9 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -22,14 +22,21 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/cache@v2 + + # Cache Gradle dependencies + - name: Setup Gradle Dependencies Cache + uses: actions/cache@v3 with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-${{ matrix.java-version }}-${{ matrix.kotlin-version }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.java-version }}-gradle- + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle', '**/*.gradle.kts') }} + + # Cache Gradle Wrapper + - name: Setup Gradle Wrapper Cache + uses: actions/cache@v3 + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }} + - name: Set up JDK ${{ matrix.java-version }} uses: actions/setup-java@v1 with: From b9dbde22fd264a67c523d4221185d4b5231c4c2c Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 25 Jul 2022 22:12:44 +0200 Subject: [PATCH 34/36] enable Gradle Build Cache, disable Gradle Welcome message --- gradle.properties | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 22747070c..f417ea718 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,10 @@ version=1.12.5-SNAPSHOT -org.gradle.parallel=true +# Enable Gradle build cache https://docs.gradle.org/current/userguide/build_cache.html +org.gradle.caching=true org.gradle.configureondemand=false +org.gradle.parallel=true +# disable annoying Gradle Welcome in CI/CD +org.gradle.welcome=never org.gradle.jvmargs=-XX:MaxMetaspaceSize=768m # localrepo=build/mockk-repo localrepo=/Users/raibaz/.m2/repository From 6d810bad3c051aeba62daabd09907622c5409e34 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 25 Jul 2022 22:25:46 +0200 Subject: [PATCH 35/36] use the same cache settings for android tests, update concurrency (hopefully android and kt tests run in parallel again?) --- .github/workflows/gradle.yml | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index f7f31d7d9..6ab1e5588 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -6,8 +6,9 @@ on: pull_request: branches: [ master ] +# This allows a subsequently queued workflow run to interrupt previous runs concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' cancel-in-progress: true jobs: @@ -23,14 +24,12 @@ jobs: steps: - uses: actions/checkout@v2 - # Cache Gradle dependencies - name: Setup Gradle Dependencies Cache uses: actions/cache@v3 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle', '**/*.gradle.kts') }} - # Cache Gradle Wrapper - name: Setup Gradle Wrapper Cache uses: actions/cache@v3 with: @@ -59,14 +58,19 @@ jobs: distribution: 'adopt' java-version: '11' - uses: actions/checkout@v2 - - uses: actions/cache@v2 + + - name: Setup Gradle Dependencies Cache + uses: actions/cache@v3 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle', '**/*.gradle.kts') }} + + - name: Setup Gradle Wrapper Cache + uses: actions/cache@v3 with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-${{ matrix.api-level }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.api-level }}-gradle- + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }} + - name: run tests uses: reactivecircus/android-emulator-runner@v2 with: From 50bed947ae3454f104da20724523d5c1a269f22a Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 25 Jul 2022 22:39:24 +0200 Subject: [PATCH 36/36] add a timeout of 30 minutes (just in case...) --- .github/workflows/gradle.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 6ab1e5588..aa2fea9db 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -13,6 +13,7 @@ concurrency: jobs: tests: + runs-on: ubuntu-latest strategy: matrix: java-version: [ 11, 17, 18 ] # test LTS versions, and the newest @@ -20,7 +21,7 @@ jobs: kotlin-ir-enabled: [ true, false ] # in case one JDK fails, we still want to see results from others fail-fast: false - runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v2 @@ -52,6 +53,7 @@ jobs: strategy: matrix: api-level: [ 28, 29 ] + timeout-minutes: 30 steps: - uses: actions/setup-java@v2 with: