diff --git a/jvm/example-kotest/build.gradle.kts b/jvm/example-kotest/build.gradle.kts index 248917e0a..178f67c28 100644 --- a/jvm/example-kotest/build.gradle.kts +++ b/jvm/example-kotest/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { } tasks.test { useJUnitPlatform() + systemProperty("kotest.framework.config.fqn", "com.example.kotest.KotestConfig") environment(properties.filter { it.key == "selfie" || it.key == "OPENAI_API_KEY" }) inputs.files(fileTree("src/test") { include("**/*.ss") diff --git a/jvm/example-kotest/src/test/kotlin/com/example/kotest/KotestConfig.kt b/jvm/example-kotest/src/test/kotlin/com/example/kotest/KotestConfig.kt index c16584b60..901a2248b 100644 --- a/jvm/example-kotest/src/test/kotlin/com/example/kotest/KotestConfig.kt +++ b/jvm/example-kotest/src/test/kotlin/com/example/kotest/KotestConfig.kt @@ -3,5 +3,5 @@ package com.example.kotest import com.diffplug.selfie.kotest.SelfieExtension class KotestConfig : io.kotest.core.config.AbstractProjectConfig() { - override fun extensions() = listOf(SelfieExtension(this)) + override val extensions = listOf(SelfieExtension(this)) } \ No newline at end of file diff --git a/jvm/gradle.properties b/jvm/gradle.properties index 83dfda6f3..557243c9a 100644 --- a/jvm/gradle.properties +++ b/jvm/gradle.properties @@ -9,7 +9,7 @@ ver_JUNIT_PIONEER=2.3.0 ver_OKIO=3.16.0 ver_KOTLIN_TEST=2.0.0 ver_KOTLIN_SERIALIZATION=1.9.0 -# Kotest 5.6.0 is the oldest that we support -ver_KOTEST=5.6.0 +# Kotest 6.0.0 is the oldest that we support +ver_KOTEST=6.1.0 ver_JVM_TARGET=11 \ No newline at end of file diff --git a/jvm/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/SelfieExtension.kt b/jvm/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/SelfieExtension.kt index aed9aee40..b15fb563e 100644 --- a/jvm/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/SelfieExtension.kt +++ b/jvm/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/SelfieExtension.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 DiffPlug + * Copyright (C) 2024-2026 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import io.kotest.core.listeners.BeforeSpecListener import io.kotest.core.listeners.FinalizeSpecListener import io.kotest.core.spec.Spec import io.kotest.core.test.TestCase -import io.kotest.core.test.TestResult +import io.kotest.engine.test.TestResult import kotlin.reflect.KClass import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.withContext @@ -43,11 +43,11 @@ class SelfieExtension(projectConfig: AbstractProjectConfig) : execute: suspend (TestCase) -> TestResult ): TestResult { val file = SnapshotSystemJUnit5.forClass(testCase.spec::class.java.name) - val coroutineLocal = CoroutineDiskStorage(DiskStorageJUnit5(file, testCase.name.testName)) + val coroutineLocal = CoroutineDiskStorage(DiskStorageJUnit5(file, testCase.name.name)) return withContext(currentCoroutineContext() + coroutineLocal) { - file.startTest(testCase.name.testName, false) + file.startTest(testCase.name.name, false) val result = execute(testCase) - file.finishedTestWithSuccess(testCase.name.testName, false, result.isSuccess) + file.finishedTestWithSuccess(testCase.name.name, false, result.isSuccess) result } } @@ -58,8 +58,8 @@ class SelfieExtension(projectConfig: AbstractProjectConfig) : val file = SnapshotSystemJUnit5.forClass(kclass.java.name) results.entries.forEach { if (it.value.isIgnored) { - file.startTest(it.key.name.testName, false) - file.finishedTestWithSuccess(it.key.name.testName, false, false) + file.startTest(it.key.name.name, false) + file.finishedTestWithSuccess(it.key.name.name, false, false) } } SnapshotSystemJUnit5.forClass(kclass.java.name) diff --git a/jvm/selfie-runner-kotest/src/commonMain/kotlin/com/diffplug/selfie/kotest/FSOkio.kt b/jvm/selfie-runner-kotest/src/commonMain/kotlin/com/diffplug/selfie/kotest/FSOkio.kt index 366fc526a..5bdac324e 100644 --- a/jvm/selfie-runner-kotest/src/commonMain/kotlin/com/diffplug/selfie/kotest/FSOkio.kt +++ b/jvm/selfie-runner-kotest/src/commonMain/kotlin/com/diffplug/selfie/kotest/FSOkio.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 DiffPlug + * Copyright (C) 2024-2026 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,18 +18,19 @@ package com.diffplug.selfie.kotest import com.diffplug.selfie.guts.FS import com.diffplug.selfie.guts.TypedPath import io.kotest.assertions.Actual -import io.kotest.assertions.Exceptions +import io.kotest.assertions.AssertionErrorBuilder import io.kotest.assertions.Expected import io.kotest.assertions.print.Printed import okio.FileSystem import okio.Path.Companion.toPath -expect internal val FS_SYSTEM: FileSystem +internal expect val FS_SYSTEM: FileSystem internal fun TypedPath.toPath(): okio.Path = absolutePath.toPath() internal object FSOkio : FS { override fun fileExists(typedPath: TypedPath): Boolean = FS_SYSTEM.metadataOrNull(typedPath.toPath())?.isRegularFile ?: false + /** Walks the files (not directories) which are children and grandchildren of the given path. */ override fun fileWalk(typedPath: TypedPath, walk: (Sequence) -> T): T = walk( @@ -40,9 +41,11 @@ internal object FSOkio : FS { FS_SYSTEM.read(typedPath.toPath()) { readByteArray() } override fun fileWriteBinary(typedPath: TypedPath, content: ByteArray): Unit = FS_SYSTEM.write(typedPath.toPath()) { write(content) } + /** Creates an assertion failed exception to throw. */ override fun assertFailed(message: String, expected: Any?, actual: Any?): Throwable = - if (expected == null && actual == null) Exceptions.createAssertionError(message, null) + if (expected == null && actual == null) + AssertionErrorBuilder.create().withMessage(message).build() else { val expectedStr = nullableToString(expected, "") val actualStr = nullableToString(actual, "") @@ -55,10 +58,11 @@ internal object FSOkio : FS { comparisonAssertion(message, expectedStr, actualStr) } } - private fun nullableToString(any: Any?, onNull: String): String = - any?.let { it.toString() } ?: onNull + private fun nullableToString(any: Any?, onNull: String): String = any?.toString() ?: onNull private fun comparisonAssertion(message: String, expected: String, actual: String): Throwable { - return Exceptions.createAssertionError( - message, null, Expected(Printed((expected))), Actual(Printed((actual)))) + return AssertionErrorBuilder.create() + .withMessage(message) + .withValues(Expected(Printed((expected))), Actual(Printed((actual)))) + .build() } } diff --git a/jvm/selfie-runner-kotest/src/commonMain/kotlin/com/diffplug/selfie/kotest/SelfieExtension.kt b/jvm/selfie-runner-kotest/src/commonMain/kotlin/com/diffplug/selfie/kotest/SelfieExtension.kt index ffd5fb653..3ee9a74b5 100644 --- a/jvm/selfie-runner-kotest/src/commonMain/kotlin/com/diffplug/selfie/kotest/SelfieExtension.kt +++ b/jvm/selfie-runner-kotest/src/commonMain/kotlin/com/diffplug/selfie/kotest/SelfieExtension.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 DiffPlug + * Copyright (C) 2024-2026 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import io.kotest.core.listeners.FinalizeSpecListener import io.kotest.core.source.SourceRef import io.kotest.core.spec.Spec import io.kotest.core.test.TestCase -import io.kotest.core.test.TestResult +import io.kotest.engine.test.TestResult import kotlin.jvm.JvmStatic import kotlin.reflect.KClass import kotlinx.coroutines.currentCoroutineContext @@ -62,18 +62,19 @@ class SelfieExtension( val classOrFilename: String = when (val source = testCase.source) { is SourceRef.ClassSource -> source.fqn - is SourceRef.FileSource -> source.fileName + is SourceRef.ClassLineSource -> source.fqn is SourceRef.None -> TODO("Handle SourceRef.None") } return system.forClassOrFilename(classOrFilename) } + /** Called for every test method. */ override suspend fun intercept( testCase: TestCase, execute: suspend (TestCase) -> TestResult ): TestResult { val file = snapshotFileFor(testCase) - val testName = testCase.name.testName + val testName = testCase.name.name val coroutineLocal = CoroutineDiskStorage(DiskStorageKotest(file, testName)) return withContext(currentCoroutineContext() + coroutineLocal) { file.startTest(testName) @@ -89,8 +90,8 @@ class SelfieExtension( val file = results.keys.map { snapshotFileFor(it) }.firstOrNull() ?: return results.entries.forEach { if (it.value.isIgnored) { - file.startTest(it.key.name.testName) - file.finishedTestWithSuccess(it.key.name.testName, false) + file.startTest(it.key.name.name) + file.finishedTestWithSuccess(it.key.name.name, false) } } file.finishedClassWithSuccess(results.entries.all { it.value.isSuccess }) diff --git a/jvm/undertest-junit5-kotest/build.gradle b/jvm/undertest-junit5-kotest/build.gradle index e45f5e401..43fc8f05d 100644 --- a/jvm/undertest-junit5-kotest/build.gradle +++ b/jvm/undertest-junit5-kotest/build.gradle @@ -39,4 +39,5 @@ test { // defaults to 'write' systemProperty 'selfie', findProperty('selfie') systemProperty 'selfie.settings', findProperty('selfie.settings') + systemProperty 'kotest.framework.config.fqn', 'undertest.junit5.JunitKotestProjectConfig' } \ No newline at end of file diff --git a/jvm/undertest-junit5-kotest/harness/gradle.properties b/jvm/undertest-junit5-kotest/harness/gradle.properties index 520a835d1..b48cfaa78 100644 --- a/jvm/undertest-junit5-kotest/harness/gradle.properties +++ b/jvm/undertest-junit5-kotest/harness/gradle.properties @@ -9,4 +9,4 @@ ver_JUNIT_PIONEER=2.2.0 ver_OKIO=3.7.0 ver_KOTLIN_TEST=1.9.22 ver_KOTLIN_SERIALIZATION=1.6.3 -ver_KOTEST=5.8.0 \ No newline at end of file +ver_KOTEST=6.1.0 \ No newline at end of file diff --git a/jvm/undertest-junit5-kotest/src/test/kotlin/undertest/junit5/JunitKotestProjectConfig.kt b/jvm/undertest-junit5-kotest/src/test/kotlin/undertest/junit5/JunitKotestProjectConfig.kt index 574982605..6750745d9 100644 --- a/jvm/undertest-junit5-kotest/src/test/kotlin/undertest/junit5/JunitKotestProjectConfig.kt +++ b/jvm/undertest-junit5-kotest/src/test/kotlin/undertest/junit5/JunitKotestProjectConfig.kt @@ -4,5 +4,5 @@ import com.diffplug.selfie.junit5.SelfieExtension import io.kotest.core.config.AbstractProjectConfig class JunitKotestProjectConfig : AbstractProjectConfig() { - override fun extensions() = listOf(SelfieExtension(this)) + override val extensions = listOf(SelfieExtension(this)) } diff --git a/jvm/undertest-junit5-kotest/src/test/kotlin/undertest/junit5/UT_KotestConcurrencyStressTest.kt b/jvm/undertest-junit5-kotest/src/test/kotlin/undertest/junit5/UT_KotestConcurrencyStressTest.kt index bdabe2565..540b9ddaf 100644 --- a/jvm/undertest-junit5-kotest/src/test/kotlin/undertest/junit5/UT_KotestConcurrencyStressTest.kt +++ b/jvm/undertest-junit5-kotest/src/test/kotlin/undertest/junit5/UT_KotestConcurrencyStressTest.kt @@ -1,12 +1,15 @@ package undertest.junit5 import com.diffplug.selfie.coroutines.expectSelfie +import io.kotest.common.ExperimentalKotest import io.kotest.core.spec.style.FunSpec +import io.kotest.engine.concurrency.TestExecutionMode import kotlinx.coroutines.delay +@OptIn(ExperimentalKotest::class) class UT_KotestConcurrencyStressTest : FunSpec({ - concurrency = 100 + testExecutionMode = TestExecutionMode.LimitedConcurrency(100) for (d in 1..1000) { val digit = d test(String.format("test %04d", digit)) { diff --git a/jvm/undertest-kotest/src/commonTest/kotlin/undertest/kotest/UT_ConcurrencyStressTest.kt b/jvm/undertest-kotest/src/commonTest/kotlin/undertest/kotest/UT_ConcurrencyStressTest.kt index 995718021..65760c78f 100644 --- a/jvm/undertest-kotest/src/commonTest/kotlin/undertest/kotest/UT_ConcurrencyStressTest.kt +++ b/jvm/undertest-kotest/src/commonTest/kotlin/undertest/kotest/UT_ConcurrencyStressTest.kt @@ -1,12 +1,15 @@ package undertest.kotest import com.diffplug.selfie.coroutines.expectSelfie +import io.kotest.common.ExperimentalKotest import io.kotest.core.spec.style.FunSpec +import io.kotest.engine.concurrency.TestExecutionMode import kotlinx.coroutines.delay +@OptIn(ExperimentalKotest::class) class UT_ConcurrencyStressTest : FunSpec({ - concurrency = 100 + testExecutionMode = TestExecutionMode.LimitedConcurrency(100) for (d in 1..1000) { val digit = d test(String.format("test %04d", digit)) { diff --git a/jvm/undertest-kotest/src/jvmTest/kotlin/kotest/KotestProjectConfig.kt b/jvm/undertest-kotest/src/jvmTest/kotlin/kotest/KotestProjectConfig.kt index c15b38e8b..9632653e2 100644 --- a/jvm/undertest-kotest/src/jvmTest/kotlin/kotest/KotestProjectConfig.kt +++ b/jvm/undertest-kotest/src/jvmTest/kotlin/kotest/KotestProjectConfig.kt @@ -4,5 +4,5 @@ import com.diffplug.selfie.kotest.SelfieExtension import io.kotest.core.config.AbstractProjectConfig object KotestProjectConfig : AbstractProjectConfig() { - override fun extensions() = listOf(SelfieExtension(this)) + override val extensions = listOf(SelfieExtension(this)) } diff --git a/selfie.dev/src/pages/jvm/kotest.mdx b/selfie.dev/src/pages/jvm/kotest.mdx index b748affb1..8dcc5c81f 100644 --- a/selfie.dev/src/pages/jvm/kotest.mdx +++ b/selfie.dev/src/pages/jvm/kotest.mdx @@ -40,6 +40,9 @@ class ProjectConfig : AbstractProjectConfig() { } ``` +Note that your `AbstractProjectConfig` must either have the fully qualified name of `io.kotest.provided.ProjectConfig` or the system property `kotest.framework.config.fqn` must be set. +See [Kotest docs](https://kotest.io/docs/framework/project-config.html) for more info. + ## Selfie and coroutines In a regular JUnit 5 test, you call `Selfie.expectSelfie(...)`, like so