[Test] Add wrapping of failures from different parts of test pipeline

This commit is contained in:
Dmitriy Novozhilov
2021-05-14 14:53:36 +03:00
committed by TeamCityServer
parent 6aca0bb374
commit 27f0d938c9
10 changed files with 124 additions and 64 deletions
@@ -1,14 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.test
class ExceptionFromTestError(cause: Throwable) : AssertionError(cause) {
override val message: String
get() = "Exception was thrown"
override val cause: Throwable
get() = super.cause!!
}
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.IOException
class TestRunner(private val testConfiguration: TestConfiguration) {
private val failedAssertions = mutableListOf<Throwable>()
private val failedAssertions = mutableListOf<WrappedException>()
fun runTest(@TestDataFile testDataFileName: String, beforeDispose: (TestConfiguration) -> Unit = {}) {
try {
@@ -46,7 +46,9 @@ class TestRunner(private val testConfiguration: TestConfiguration) {
}
} catch (e: ExceptionFromModuleStructureTransformer) {
services.register(TestModuleStructure::class, e.alreadyParsedModuleStructure)
val exception = filterFailedExceptions(listOf(e.cause)).singleOrNull() ?: return
val exception = filterFailedExceptions(
listOf(WrappedException.FromModuleStructureTransformer(e.cause))
).singleOrNull() ?: return
throw exception
}
@@ -60,18 +62,26 @@ class TestRunner(private val testConfiguration: TestConfiguration) {
val modules = moduleStructure.modules
val dependencyProvider = DependencyProviderImpl(services, modules)
services.registerDependencyProvider(dependencyProvider)
var failedException: Throwable? = null
var failedException: WrappedException? = null
try {
for (module in modules) {
val shouldProcessNextModules = processModule(services, module, dependencyProvider, moduleStructure)
if (!shouldProcessNextModules) break
}
} catch (e: Throwable) {
} catch (e: WrappedException) {
failedException = e
} catch (e: Exception) {
throw IllegalStateException("Unexpected exception type. Only WrappedException are expected here", e)
}
for (handler in testConfiguration.getAllHandlers()) {
withAssertionCatching {
val wrapperFactory = when (handler) {
is FrontendOutputHandler -> WrappedException::FromFrontendHandler
is BackendInputHandler -> WrappedException::FromBackendHandler
is BinaryArtifactHandler -> WrappedException::FromBinaryHandler
else -> WrappedException::FromUnknownHandler
}
withAssertionCatching(wrapperFactory) {
val thereWasAnException = failedException != null || failedAssertions.isNotEmpty()
if (handler.shouldRun(thereWasAnException)) {
handler.processAfterAllModules(thereWasAnException)
@@ -79,32 +89,35 @@ class TestRunner(private val testConfiguration: TestConfiguration) {
}
}
if (testConfiguration.metaInfoHandlerEnabled) {
withAssertionCatching(insertExceptionInStart = true) {
withAssertionCatching(WrappedException::FromMetaInfoHandler) {
globalMetadataInfoHandler.compareAllMetaDataInfos()
}
}
if (failedException != null) {
failedAssertions.add(0, ExceptionFromTestError(failedException))
failedAssertions.add(0, failedException)
}
testConfiguration.afterAnalysisCheckers.forEach {
withAssertionCatching {
withAssertionCatching(WrappedException::FromAfterAnalysisChecker) {
it.check(failedAssertions)
}
}
val filteredFailedAssertions = filterFailedExceptions(failedAssertions)
filteredFailedAssertions.firstIsInstanceOrNull<ExceptionFromTestError>()?.let {
filteredFailedAssertions.firstIsInstanceOrNull<WrappedException.FromFacade>()?.let {
throw it
}
services.assertions.assertAll(filteredFailedAssertions)
}
private fun filterFailedExceptions(failedExceptions: List<Throwable>): List<Throwable> = testConfiguration.afterAnalysisCheckers
.fold(failedExceptions) { assertions, checker ->
checker.suppressIfNeeded(assertions)
}
.map { if (it is ExceptionFromTestError) it.cause else it }
private fun filterFailedExceptions(failedExceptions: List<WrappedException>): List<Throwable> {
return testConfiguration.afterAnalysisCheckers
.fold(failedExceptions) { assertions, checker ->
checker.suppressIfNeeded(assertions)
}
.sorted()
.map { it.cause }
}
/*
* If there was failure from handler with `failureDisablesNextSteps=true` then `processModule`
@@ -121,11 +134,14 @@ class TestRunner(private val testConfiguration: TestConfiguration) {
val frontendKind = module.frontendKind
if (!frontendKind.shouldRunAnalysis) return true
val frontendArtifacts: ResultingArtifact.FrontendOutput<*> = testConfiguration.getFacade(SourcesKind, frontendKind)
.transform(module, sourcesArtifact)?.also { dependencyProvider.registerArtifact(module, it) } ?: return true
val frontendArtifacts: ResultingArtifact.FrontendOutput<*> = withExceptionWrapping(WrappedException.FromFacade::Frontend) {
testConfiguration.getFacade(SourcesKind, frontendKind)
.transform(module, sourcesArtifact)?.also { dependencyProvider.registerArtifact(module, it) }
?: return true
}
val frontendHandlers: List<AnalysisHandler<*>> = testConfiguration.getHandlers(frontendKind)
for (frontendHandler in frontendHandlers) {
val thereWasAnException = withAssertionCatching {
val thereWasAnException = withAssertionCatching(WrappedException::FromFrontendHandler) {
if (frontendHandler.shouldRun(failedAssertions.isNotEmpty())) {
frontendHandler.hackyProcess(module, frontendArtifacts)
}
@@ -136,12 +152,14 @@ class TestRunner(private val testConfiguration: TestConfiguration) {
val backendKind = services.backendKindExtractor.backendKind(module.targetBackend)
if (!backendKind.shouldRunAnalysis) return true
val backendInputInfo = testConfiguration.getFacade(frontendKind, backendKind)
.hackyTransform(module, frontendArtifacts)?.also { dependencyProvider.registerArtifact(module, it) } ?: return true
val backendInputInfo = withExceptionWrapping(WrappedException.FromFacade::Converter) {
testConfiguration.getFacade(frontendKind, backendKind)
.hackyTransform(module, frontendArtifacts)?.also { dependencyProvider.registerArtifact(module, it) } ?: return true
}
val backendHandlers: List<AnalysisHandler<*>> = testConfiguration.getHandlers(backendKind)
for (backendHandler in backendHandlers) {
val thereWasAnException = withAssertionCatching {
val thereWasAnException = withAssertionCatching(WrappedException::FromBackendHandler) {
if (backendHandler.shouldRun(failedAssertions.isNotEmpty())) {
backendHandler.hackyProcess(module, backendInputInfo)
}
@@ -151,14 +169,16 @@ class TestRunner(private val testConfiguration: TestConfiguration) {
for (artifactKind in moduleStructure.getTargetArtifactKinds(module)) {
if (!artifactKind.shouldRunAnalysis) continue
val binaryArtifact = testConfiguration.getFacade(backendKind, artifactKind)
.hackyTransform(module, backendInputInfo)?.also {
dependencyProvider.registerArtifact(module, it)
} ?: return true
val binaryArtifact = withExceptionWrapping(WrappedException.FromFacade::Backend) {
testConfiguration.getFacade(backendKind, artifactKind)
.hackyTransform(module, backendInputInfo)?.also {
dependencyProvider.registerArtifact(module, it)
} ?: return true
}
val binaryHandlers: List<AnalysisHandler<*>> = testConfiguration.getHandlers(artifactKind)
for (binaryHandler in binaryHandlers) {
val thereWasAnException = withAssertionCatching {
val thereWasAnException = withAssertionCatching(WrappedException::FromBinaryHandler) {
if (binaryHandler.shouldRun(failedAssertions.isNotEmpty())) {
binaryHandler.hackyProcess(module, binaryArtifact)
}
@@ -173,20 +193,24 @@ class TestRunner(private val testConfiguration: TestConfiguration) {
/*
* Returns true if there was an exception in block
*/
private inline fun withAssertionCatching(insertExceptionInStart: Boolean = false, block: () -> Unit): Boolean {
private inline fun withAssertionCatching(exceptionWrapper: (Throwable) -> WrappedException, block: () -> Unit): Boolean {
return try {
block()
false
} catch (e: Throwable) {
if (insertExceptionInStart) {
failedAssertions.add(0, e)
} else {
failedAssertions += e
}
failedAssertions += exceptionWrapper(e)
true
}
}
private inline fun <R> withExceptionWrapping(exceptionWrapper: (Throwable) -> WrappedException, block: () -> R): R {
return try {
block()
} catch (e: Throwable) {
throw exceptionWrapper(e)
}
}
private fun AnalysisHandler<*>.shouldRun(thereWasAnException: Boolean): Boolean {
return !(doNotRunIfThereWerePreviousFailures && thereWasAnException)
}
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.test
sealed class WrappedException(
cause: Throwable,
val priority: Int,
val additionalPriority: Int
) : Exception(cause), Comparable<WrappedException> {
sealed class FromFacade(cause: Throwable, additionalPriority: Int) : WrappedException(cause, 0, additionalPriority) {
class Frontend(cause: Throwable) : FromFacade(cause, 1)
class Converter(cause: Throwable) : FromFacade(cause, 2)
class Backend(cause: Throwable) : FromFacade(cause, 3)
override val message: String
get() = "Exception was thrown"
}
class FromMetaInfoHandler(cause: Throwable) : WrappedException(cause, 1, 1)
class FromFrontendHandler(cause: Throwable) : WrappedException(cause, 1, 1)
class FromBackendHandler(cause: Throwable) : WrappedException(cause, 1, 2)
class FromBinaryHandler(cause: Throwable) : WrappedException(cause, 1, 3)
class FromUnknownHandler(cause: Throwable) : WrappedException(cause, 1, 4)
class FromAfterAnalysisChecker(cause: Throwable) : WrappedException(cause, 2, 1)
class FromModuleStructureTransformer(cause: Throwable) : WrappedException(cause, 2, 1)
override val cause: Throwable
get() = super.cause!!
override fun compareTo(other: WrappedException): Int {
if (priority == other.priority) {
return additionalPriority - other.additionalPriority
}
return priority - other.priority
}
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.test.model
import org.jetbrains.kotlin.test.WrappedException
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.services.TestServices
@@ -12,7 +13,9 @@ abstract class AfterAnalysisChecker(protected val testServices: TestServices) {
open val directives: List<DirectivesContainer>
get() = emptyList()
open fun check(failedAssertions: List<Throwable>) {}
open fun check(failedAssertions: List<WrappedException>) {}
open fun suppressIfNeeded(failedAssertions: List<Throwable>): List<Throwable> = failedAssertions
open fun suppressIfNeeded(failedAssertions: List<WrappedException>): List<WrappedException> = failedAssertions
protected fun Throwable.wrap(): WrappedException = WrappedException.FromAfterAnalysisChecker(this)
}