blob: ea278ef6380a1b73a48fabb6d46e63819727937a [file] [log] [blame]
/*
* 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
import com.intellij.openapi.util.Disposer
import com.intellij.testFramework.TestDataFile
import org.jetbrains.kotlin.test.model.AnalysisHandler
import org.jetbrains.kotlin.test.model.ResultingArtifact
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.IOException
class TestRunner(private val testConfiguration: TestConfiguration) {
companion object {
fun AnalysisHandler<*>.shouldRun(thereWasAnException: Boolean): Boolean {
return !(doNotRunIfThereWerePreviousFailures && thereWasAnException)
}
}
private val allFailedExceptions = mutableListOf<WrappedException>()
private val allRanHandlers = mutableSetOf<AnalysisHandler<*>>()
fun runTest(@TestDataFile testDataFileName: String, beforeDispose: (TestConfiguration) -> Unit = {}) {
try {
runTestImpl(testDataFileName)
} finally {
try {
testConfiguration.testServices.temporaryDirectoryManager.cleanupTemporaryDirectories()
} catch (_: IOException) {
// ignored
}
beforeDispose(testConfiguration)
Disposer.dispose(testConfiguration.rootDisposable)
}
}
private fun runTestImpl(@TestDataFile testDataFileName: String) {
val services = testConfiguration.testServices
@Suppress("NAME_SHADOWING")
val testDataFileName = testConfiguration.metaTestConfigurators.fold(testDataFileName) { fileName, configurator ->
configurator.transformTestDataPath(fileName)
}
val moduleStructure = try {
testConfiguration.moduleStructureExtractor.splitTestDataByModules(
testDataFileName,
testConfiguration.directives,
).also {
services.register(TestModuleStructure::class, it)
}
} catch (e: ExceptionFromModuleStructureTransformer) {
services.register(TestModuleStructure::class, e.alreadyParsedModuleStructure)
val exception = filterFailedExceptions(
listOf(WrappedException.FromModuleStructureTransformer(e.cause))
).singleOrNull() ?: return
throw exception
}
testConfiguration.metaTestConfigurators.forEach {
if (it.shouldSkipTest()) return
}
runTestPipeline(moduleStructure, services)
}
fun runTestPipeline(moduleStructure: TestModuleStructure, services: TestServices) {
val globalMetadataInfoHandler = testConfiguration.testServices.globalMetadataInfoHandler
globalMetadataInfoHandler.parseExistingMetadataInfosFromAllSources()
val modules = moduleStructure.modules
val dependencyProvider = DependencyProviderImpl(services, modules)
services.registerDependencyProvider(dependencyProvider)
testConfiguration.preAnalysisHandlers.forEach { preprocessor ->
preprocessor.preprocessModuleStructure(moduleStructure)
}
for (module in modules) {
val shouldProcessNextModules = processModule(module, dependencyProvider)
if (!shouldProcessNextModules) break
}
for (handler in allRanHandlers) {
val wrapperFactory: (Throwable) -> WrappedException = { WrappedException.FromHandler(it, handler) }
withAssertionCatching(wrapperFactory) {
val thereWasAnException = allFailedExceptions.isNotEmpty()
if (handler.shouldRun(thereWasAnException)) {
handler.processAfterAllModules(thereWasAnException)
}
}
}
if (testConfiguration.metaInfoHandlerEnabled) {
withAssertionCatching(WrappedException::FromMetaInfoHandler) {
globalMetadataInfoHandler.compareAllMetaDataInfos()
}
}
testConfiguration.afterAnalysisCheckers.forEach {
withAssertionCatching(WrappedException::FromAfterAnalysisChecker) {
it.check(allFailedExceptions)
}
}
reportFailures(services)
}
fun reportFailures(services: TestServices) {
val filteredFailedAssertions = filterFailedExceptions(allFailedExceptions)
filteredFailedAssertions.firstIsInstanceOrNull<WrappedException.FromFacade>()?.let {
throw it
}
services.assertions.assertAll(filteredFailedAssertions)
}
/*
* Returns false if next modules should be not processed
*/
fun processModule(
module: TestModule,
dependencyProvider: DependencyProviderImpl
): Boolean {
var inputArtifact = testConfiguration.startingArtifactFactory.invoke(module)
for (step in testConfiguration.steps) {
if (!step.shouldProcessModule(module, inputArtifact)) continue
when (val result = step.hackyProcessModule(module, inputArtifact, allFailedExceptions.isNotEmpty())) {
is TestStep.StepResult.Artifact<*> -> {
require(step is TestStep.FacadeStep<*, *>)
if (step.inputArtifactKind != step.outputArtifactKind) {
dependencyProvider.registerArtifact(module, result.outputArtifact)
}
inputArtifact = result.outputArtifact
}
is TestStep.StepResult.ErrorFromFacade -> {
allFailedExceptions += result.exception
return false
}
is TestStep.StepResult.HandlersResult -> {
val (exceptionsFromHandlers, shouldRunNextSteps) = result
require(step is TestStep.HandlersStep<*>)
allRanHandlers += step.handlers
allFailedExceptions += exceptionsFromHandlers
if (!shouldRunNextSteps) {
return false
}
}
is TestStep.StepResult.NoArtifactFromFacade -> return false
}
}
return true
}
/*
* Returns true if there was an exception in block
*/
private inline fun withAssertionCatching(exceptionWrapper: (Throwable) -> WrappedException, block: () -> Unit): Boolean {
return try {
block()
false
} catch (e: Throwable) {
allFailedExceptions += exceptionWrapper(e)
true
}
}
private fun filterFailedExceptions(failedExceptions: List<WrappedException>): List<Throwable> {
return testConfiguration.afterAnalysisCheckers
.fold(failedExceptions) { assertions, checker ->
checker.suppressIfNeeded(assertions)
}
.sorted()
.map { it.cause }
}
// -------------------------------------- hacks --------------------------------------
private fun TestStep<*, *>.hackyProcessModule(
module: TestModule,
inputArtifact: ResultingArtifact<*>,
thereWereExceptionsOnPreviousSteps: Boolean
): TestStep.StepResult<*> {
@Suppress("UNCHECKED_CAST")
return (this as TestStep<ResultingArtifact.Source, *>)
.processModule(module, inputArtifact as ResultingArtifact<ResultingArtifact.Source>, thereWereExceptionsOnPreviousSteps)
}
private fun <I : ResultingArtifact<I>> TestStep<I, *>.processModule(
module: TestModule,
artifact: ResultingArtifact<I>,
thereWereExceptionsOnPreviousSteps: Boolean
): TestStep.StepResult<*> {
@Suppress("UNCHECKED_CAST")
return processModule(module, artifact as I, thereWereExceptionsOnPreviousSteps)
}
}