diff --git a/compiler/tests-common/build.gradle.kts b/compiler/tests-common/build.gradle.kts index e9309a9b122..19d17c4f937 100644 --- a/compiler/tests-common/build.gradle.kts +++ b/compiler/tests-common/build.gradle.kts @@ -97,6 +97,7 @@ dependencies { testCompile(intellijDep()) { includeJars("platform-util-ui", "platform-concurrency", "platform-objectSerializer") } testCompile(intellijDep()) { includeJars("platform-ide-util-io") } + testApiJUnit5() } tasks.withType> { diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/muteWithDatabase.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/test/muteWithDatabase.kt index b7a08e1743a..e94f1c2487e 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/test/muteWithDatabase.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/muteWithDatabase.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.test import junit.framework.TestCase import org.jetbrains.kotlin.test.mutes.* import org.junit.internal.runners.statements.InvokeMethod +import org.junit.jupiter.api.extension.* import org.junit.runner.Runner import org.junit.runner.notification.RunNotifier import org.junit.runners.BlockJUnit4ClassRunner @@ -16,6 +17,7 @@ import org.junit.runners.model.Statement import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters import org.junit.runners.parameterized.ParametersRunnerFactory import org.junit.runners.parameterized.TestWithParameters +import java.lang.reflect.Method internal fun wrapWithMuteInDatabase(testCase: TestCase, f: () -> Unit): (() -> Unit)? { return wrapWithMuteInDatabase(testCase.javaClass, testCase.name, f) @@ -95,3 +97,102 @@ fun TestCase.runTest(test: () -> Unit) { } annotation class WithMutedInDatabaseRunTest + +/** + * Extension for JUnit 5 tests adding mute-in database support to ignore flaky/failed tests. + * + * Just add it to the test class or test method. + */ +@Target(AnnotationTarget.CLASS, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +@ExtendWith(MuteInCondition::class, MuteInTestWatcher::class, MuteInInvocationInterceptor::class) +annotation class WithMuteInDatabase + +private val ExtensionContext.testClassNullable get() = testClass.orElseGet { null } +private val ExtensionContext.testMethodNullable get() = testMethod.orElseGet { null } + +class MuteInCondition : ExecutionCondition { + override fun evaluateExecutionCondition( + context: ExtensionContext + ): ConditionEvaluationResult { + val testClass = context.testClassNullable + val testMethod = context.testMethodNullable + + return if (testClass != null && + testMethod != null && + isMutedInDatabaseWithLog(testClass, testMethod.name) + ) { + ConditionEvaluationResult.disabled("Muted") + } else { + enabled + } + } + + companion object { + private val enabled = ConditionEvaluationResult.enabled("Not found in mute-in database") + } +} + +class MuteInTestWatcher : TestWatcher { + override fun testFailed( + context: ExtensionContext, + cause: Throwable + ) { + val testClass = context.testClassNullable + val testMethod = context.testMethodNullable + if (testClass != null && + testMethod != null + ) { + DO_AUTO_MUTE?.muteTest( + testKey(testClass, testMethod.name) + ) + } + } +} + +class MuteInInvocationInterceptor : InvocationInterceptor { + override fun interceptTestTemplateMethod( + invocation: InvocationInterceptor.Invocation, + invocationContext: ReflectiveInvocationContext, + extensionContext: ExtensionContext + ) { + interceptWithMuteInDatabase(invocation, extensionContext) + } + + override fun interceptTestMethod( + invocation: InvocationInterceptor.Invocation, + invocationContext: ReflectiveInvocationContext, + extensionContext: ExtensionContext + ) { + interceptWithMuteInDatabase(invocation, extensionContext) + } + + private fun interceptWithMuteInDatabase( + invocation: InvocationInterceptor.Invocation, + extensionContext: ExtensionContext + ) { + val testClass = extensionContext.testClassNullable + val testMethod = extensionContext.testMethodNullable + if (testClass != null && + testMethod != null + ) { + val mutedTest = getMutedTest(testClass, testMethod.name) + if (mutedTest != null && + isPresentedInDatabaseWithoutFailMarker(mutedTest) + ) { + if (mutedTest.isFlaky) { + invocation.proceed() + return + } else { + invertMutedTestResultWithLog( + f = { invocation.proceed() }, + testKey = testKey(testMethod.declaringClass, mutedTest.methodKey) + ) + return + } + } + } + + invocation.proceed() + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/KGPBaseTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/KGPBaseTest.kt index 51b3f0300e1..92d117ace9b 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/KGPBaseTest.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/KGPBaseTest.kt @@ -11,6 +11,7 @@ import org.gradle.internal.impldep.org.junit.platform.commons.support.Annotation import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.KOTLIN_VERSION import org.jetbrains.kotlin.gradle.utils.minSupportedGradleVersion +import org.jetbrains.kotlin.test.WithMuteInDatabase import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -28,6 +29,7 @@ import kotlin.streams.asStream * Base class for all Kotlin Gradle plugin integration tests. */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) +@WithMuteInDatabase abstract class KGPBaseTest { open val defaultBuildOptions = BuildOptions()