diff --git a/kotest-framework/kotest-framework-engine/src/commonMain/kotlin/io/kotest/engine/SpecInterceptors.kt b/kotest-framework/kotest-framework-engine/src/commonMain/kotlin/io/kotest/engine/SpecInterceptors.kt new file mode 100644 index 00000000000..29cd252ec72 --- /dev/null +++ b/kotest-framework/kotest-framework-engine/src/commonMain/kotlin/io/kotest/engine/SpecInterceptors.kt @@ -0,0 +1,10 @@ +package io.kotest.engine + +import io.kotest.common.KotestInternal +import io.kotest.engine.spec.interceptor.SpecInterceptor + +/** + * Returns the [SpecInterceptor]s that should be used for this platform. + */ +@KotestInternal +internal expect fun specInterceptorsForPlatform(): List diff --git a/kotest-framework/kotest-framework-engine/src/commonMain/kotlin/io/kotest/engine/spec/SpecExecutor.kt b/kotest-framework/kotest-framework-engine/src/commonMain/kotlin/io/kotest/engine/spec/SpecExecutor.kt index 7045e99f7c5..68428b2a4e7 100644 --- a/kotest-framework/kotest-framework-engine/src/commonMain/kotlin/io/kotest/engine/spec/SpecExecutor.kt +++ b/kotest-framework/kotest-framework-engine/src/commonMain/kotlin/io/kotest/engine/spec/SpecExecutor.kt @@ -31,6 +31,7 @@ import io.kotest.engine.spec.interceptor.SpecRefInterceptor import io.kotest.engine.spec.interceptor.SpecStartedInterceptor import io.kotest.engine.spec.interceptor.SystemPropertySpecFilterInterceptor import io.kotest.engine.spec.interceptor.TagsExcludedSpecInterceptor +import io.kotest.engine.specInterceptorsForPlatform import io.kotest.mpp.Logger import io.kotest.mpp.bestName import kotlin.reflect.KClass @@ -98,7 +99,7 @@ class SpecExecutor( ProjectContextInterceptor(context.toProjectContext()), SpecExtensionInterceptor(context.configuration.registry), ConfigurationInContextInterceptor(context.configuration), - ) + ) + specInterceptorsForPlatform() val initial: suspend (Spec) -> Result> = { try { diff --git a/kotest-framework/kotest-framework-engine/src/desktopMain/kotlin/io/kotest/engine/TestEngineInterceptors.kt b/kotest-framework/kotest-framework-engine/src/desktopMain/kotlin/io/kotest/engine/interceptors.kt similarity index 87% rename from kotest-framework/kotest-framework-engine/src/desktopMain/kotlin/io/kotest/engine/TestEngineInterceptors.kt rename to kotest-framework/kotest-framework-engine/src/desktopMain/kotlin/io/kotest/engine/interceptors.kt index b0aa565ab66..fe6227d1a2b 100644 --- a/kotest-framework/kotest-framework-engine/src/desktopMain/kotlin/io/kotest/engine/TestEngineInterceptors.kt +++ b/kotest-framework/kotest-framework-engine/src/desktopMain/kotlin/io/kotest/engine/interceptors.kt @@ -10,6 +10,7 @@ import io.kotest.engine.interceptors.SpecSortEngineInterceptor import io.kotest.engine.interceptors.TestDslStateInterceptor import io.kotest.engine.interceptors.TestEngineInitializedInterceptor import io.kotest.engine.interceptors.TestEngineStartedFinishedInterceptor +import io.kotest.engine.spec.interceptor.SpecInterceptor @KotestInternal internal actual fun testEngineInterceptors(): List { @@ -24,3 +25,7 @@ internal actual fun testEngineInterceptors(): List { TestEngineInitializedInterceptor, ) } + +@KotestInternal +internal actual fun specInterceptorsForPlatform(): List = + listOf() diff --git a/kotest-framework/kotest-framework-engine/src/jsMain/kotlin/io/kotest/engine/interceptors.kt b/kotest-framework/kotest-framework-engine/src/jsMain/kotlin/io/kotest/engine/interceptors.kt index b0aa565ab66..fe6227d1a2b 100644 --- a/kotest-framework/kotest-framework-engine/src/jsMain/kotlin/io/kotest/engine/interceptors.kt +++ b/kotest-framework/kotest-framework-engine/src/jsMain/kotlin/io/kotest/engine/interceptors.kt @@ -10,6 +10,7 @@ import io.kotest.engine.interceptors.SpecSortEngineInterceptor import io.kotest.engine.interceptors.TestDslStateInterceptor import io.kotest.engine.interceptors.TestEngineInitializedInterceptor import io.kotest.engine.interceptors.TestEngineStartedFinishedInterceptor +import io.kotest.engine.spec.interceptor.SpecInterceptor @KotestInternal internal actual fun testEngineInterceptors(): List { @@ -24,3 +25,7 @@ internal actual fun testEngineInterceptors(): List { TestEngineInitializedInterceptor, ) } + +@KotestInternal +internal actual fun specInterceptorsForPlatform(): List = + listOf() diff --git a/kotest-framework/kotest-framework-engine/src/jvmMain/kotlin/io/kotest/engine/interceptors.kt b/kotest-framework/kotest-framework-engine/src/jvmMain/kotlin/io/kotest/engine/interceptors.kt index 0c050abbb70..50feeed29e1 100644 --- a/kotest-framework/kotest-framework-engine/src/jvmMain/kotlin/io/kotest/engine/interceptors.kt +++ b/kotest-framework/kotest-framework-engine/src/jvmMain/kotlin/io/kotest/engine/interceptors.kt @@ -4,6 +4,7 @@ import io.kotest.common.KotestInternal import io.kotest.engine.interceptors.DumpConfigInterceptor import io.kotest.engine.interceptors.EmptyTestSuiteInterceptor import io.kotest.engine.interceptors.EngineInterceptor +import io.kotest.engine.interceptors.MarkAbortedExceptionsAsSkippedTestInterceptor import io.kotest.engine.interceptors.ProjectExtensionEngineInterceptor import io.kotest.engine.interceptors.ProjectListenerEngineInterceptor import io.kotest.engine.interceptors.ProjectTimeoutEngineInterceptor @@ -12,6 +13,7 @@ import io.kotest.engine.interceptors.TestDslStateInterceptor import io.kotest.engine.interceptors.TestEngineInitializedInterceptor import io.kotest.engine.interceptors.TestEngineStartedFinishedInterceptor import io.kotest.engine.interceptors.WriteFailuresInterceptor +import io.kotest.engine.spec.interceptor.SpecInterceptor @KotestInternal actual fun testEngineInterceptors(): List { @@ -28,3 +30,7 @@ actual fun testEngineInterceptors(): List { TestEngineInitializedInterceptor, ) } + +@KotestInternal +internal actual fun specInterceptorsForPlatform(): List = + listOf(MarkAbortedExceptionsAsSkippedTestInterceptor) diff --git a/kotest-framework/kotest-framework-engine/src/jvmMain/kotlin/io/kotest/engine/interceptors/MarkAbortedExceptionsAsSkippedTestInterceptor.kt b/kotest-framework/kotest-framework-engine/src/jvmMain/kotlin/io/kotest/engine/interceptors/MarkAbortedExceptionsAsSkippedTestInterceptor.kt new file mode 100644 index 00000000000..77661bbb5ca --- /dev/null +++ b/kotest-framework/kotest-framework-engine/src/jvmMain/kotlin/io/kotest/engine/interceptors/MarkAbortedExceptionsAsSkippedTestInterceptor.kt @@ -0,0 +1,34 @@ +package io.kotest.engine.interceptors + +import io.kotest.common.JVMOnly +import io.kotest.common.KotestInternal +import io.kotest.core.spec.Spec +import io.kotest.core.test.TestCase +import io.kotest.core.test.TestResult +import io.kotest.engine.spec.interceptor.SpecInterceptor +import org.opentest4j.TestAbortedException + +/** + * Writes failed specs to a file so that the [io.kotest.engine.spec.FailureFirstSorter] + * can use the file to run failed specs first. + * + * Note: This is a JVM only feature. + */ +@JVMOnly +internal object MarkAbortedExceptionsAsSkippedTestInterceptor : SpecInterceptor { + + override suspend fun intercept( + spec: Spec, + fn: suspend (Spec) -> Result> + ): Result> { + return fn(spec).map { success -> + success.mapValues { (_, result) -> + if (result.errorOrNull is TestAbortedException) { + TestResult.Ignored + } else { + result + } + } + } + } +} diff --git a/kotest-framework/kotest-framework-engine/src/jvmTest/kotlin/com/sksamuel/kotest/engine/interceptors/AbortedExceptionTest.kt b/kotest-framework/kotest-framework-engine/src/jvmTest/kotlin/com/sksamuel/kotest/engine/interceptors/AbortedExceptionTest.kt new file mode 100644 index 00000000000..9f7cdf63e8a --- /dev/null +++ b/kotest-framework/kotest-framework-engine/src/jvmTest/kotlin/com/sksamuel/kotest/engine/interceptors/AbortedExceptionTest.kt @@ -0,0 +1,64 @@ +package com.sksamuel.kotest.engine.interceptors + +import io.kotest.core.descriptors.Descriptor +import io.kotest.core.descriptors.DescriptorId +import io.kotest.core.names.TestName +import io.kotest.core.spec.style.FreeSpec +import io.kotest.core.test.TestCase +import io.kotest.core.test.TestResult +import io.kotest.core.test.TestType +import io.kotest.engine.interceptors.MarkAbortedExceptionsAsSkippedTestInterceptor +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.result.shouldBeSuccess +import org.opentest4j.TestAbortedException +import kotlin.time.Duration.Companion.milliseconds + +class AbortedExceptionTest : FreeSpec({ + + val fakeTestCase = TestCase( + Descriptor.TestDescriptor( + Descriptor.SpecDescriptor(DescriptorId("dummy"), DummySpec::class), + DescriptorId("test") + ), TestName("dummy"), DummySpec(), {}, type = TestType.Test + ) + + "Test should be marked as Ignored" { + val result = MarkAbortedExceptionsAsSkippedTestInterceptor.intercept(DummySpec()) { + Result.success( + mapOf(fakeTestCase to TestResult.Error(1.milliseconds, TestAbortedException())) + ) + } + + result.shouldBeSuccess() + .values + .shouldContainExactly(TestResult.Ignored) + } + + "Failure is not reclassified" { + val assertionError = AssertionError("blah") + val result = MarkAbortedExceptionsAsSkippedTestInterceptor.intercept(DummySpec()) { + Result.success( + mapOf(fakeTestCase to TestResult.Failure(1.milliseconds, assertionError)) + ) + } + + result.shouldBeSuccess() + .values + .shouldContainExactly(TestResult.Failure(1.milliseconds, assertionError)) + } + + "Successful test is not reclassified" { + val result = MarkAbortedExceptionsAsSkippedTestInterceptor.intercept(DummySpec()) { + Result.success( + mapOf(fakeTestCase to TestResult.Success(1.milliseconds)) + ) + } + + result.shouldBeSuccess() + .values + .shouldContainExactly(TestResult.Success(1.milliseconds)) + } +}) + +private class DummySpec : FreeSpec() +