[TEST] Migrate AbstractFirDiagnosticsWithLightTreeTest to new test runners

Also this commit adds AbstractTwoAttributesMetaInfoProcessor which can
  be used for reporting diagnostics with two attributes (OI/NI, PSI/Light tree)
This commit is contained in:
Dmitriy Novozhilov
2020-12-15 16:07:26 +03:00
parent 7e9deb7602
commit 49d9b85950
12 changed files with 757 additions and 313 deletions
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.test.model
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.services.ServiceRegistrationData
import org.jetbrains.kotlin.test.services.TestServices
@@ -16,6 +17,9 @@ abstract class AbstractTestFacade<I : ResultingArtifact<I>, O : ResultingArtifac
open val additionalServices: List<ServiceRegistrationData>
get() = emptyList()
open val additionalDirectives: List<DirectivesContainer>
get() = emptyList()
}
abstract class FrontendFacade<R : ResultingArtifact.FrontendOutput<R>>(
@@ -24,4 +24,15 @@ object FirDiagnosticsDirectives : SimpleDirectivesContainer() {
val FIR_IDENTICAL by directive(
description = "Contents of fir test data file and FE 1.0 are identical"
)
val USE_LIGHT_TREE by directive(
description = "Enables light tree parser instead of PSI"
)
val COMPARE_WITH_LIGHT_TREE by directive(
description = """
Enable comparing diagnostics between PSI and light tree modes
For enabling light tree mode use $USE_LIGHT_TREE directive
""".trimIndent()
)
}
@@ -6,9 +6,7 @@
package org.jetbrains.kotlin.test.frontend.classic.handlers
import org.jetbrains.kotlin.checkers.utils.DiagnosticsRenderingConfiguration
import org.jetbrains.kotlin.codeMetaInfo.model.CodeMetaInfo
import org.jetbrains.kotlin.codeMetaInfo.model.DiagnosticCodeMetaInfo
import org.jetbrains.kotlin.codeMetaInfo.model.ParsedCodeMetaInfo
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.diagnostics.Diagnostic
@@ -20,6 +18,7 @@ import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.utils.AbstractTwoAttributesMetaInfoProcessor
class ClassicDiagnosticReporter(private val testServices: TestServices) {
private val globalMetadataInfoHandler: GlobalMetadataInfoHandler
@@ -81,66 +80,21 @@ class ClassicDiagnosticReporter(private val testServices: TestServices) {
}
}
class OldNewInferenceMetaInfoProcessor(testServices: TestServices) : AdditionalMetaInfoProcessor(testServices) {
class OldNewInferenceMetaInfoProcessor(testServices: TestServices) : AbstractTwoAttributesMetaInfoProcessor(testServices) {
companion object {
const val OI = "OI"
const val NI = "NI"
}
override fun processMetaInfos(module: TestModule, file: TestFile) {
/*
* Rules for OI/NI attribute:
* ┌──────────┬──────┬──────┬──────────┐
* │ │ OI │ NI │ nothing │ <- reported
* ├──────────┼──────┼──────┼──────────┤
* │ nothing │ both │ both │ nothing │
* │ OI │ OI │ both │ OI │
* │ NI │ both │ NI │ NI │
* │ both │ both │ both │ opposite │ <- OI if NI enabled in test and vice versa
* └──────────┴──────┴──────┴──────────┘
* ^ existed
*/
if (!testServices.withNewInferenceModeEnabled()) return
val newInferenceEnabled = module.languageVersionSettings.supportsFeature(LanguageFeature.NewInference)
val (currentFlag, otherFlag) = when (newInferenceEnabled) {
true -> NI to OI
false -> OI to NI
}
val matchedExistedInfos = mutableSetOf<ParsedCodeMetaInfo>()
val matchedReportedInfos = mutableSetOf<CodeMetaInfo>()
val allReportedInfos = globalMetadataInfoHandler.getReportedMetaInfosForFile(file)
for ((_, reportedInfos) in allReportedInfos.groupBy { Triple(it.start, it.end, it.tag) }) {
val existedInfos = globalMetadataInfoHandler.getExistingMetaInfosForActualMetadata(file, reportedInfos.first())
for ((reportedInfo, existedInfo) in reportedInfos.zip(existedInfos)) {
matchedExistedInfos += existedInfo
matchedReportedInfos += reportedInfo
if (currentFlag !in reportedInfo.attributes) continue
if (currentFlag in existedInfo.attributes) continue
reportedInfo.attributes.remove(currentFlag)
}
}
override val firstAttribute: String get() = NI
override val secondAttribute: String get() = OI
if (allReportedInfos.size != matchedReportedInfos.size) {
for (info in allReportedInfos) {
if (info !in matchedReportedInfos) {
info.attributes.remove(currentFlag)
}
}
}
override fun processorEnabled(module: TestModule): Boolean {
return DiagnosticsDirectives.WITH_NEW_INFERENCE in module.directives
}
val allExistedInfos = globalMetadataInfoHandler.getExistingMetaInfosForFile(file)
if (allExistedInfos.size == matchedExistedInfos.size) return
val newInfos = allExistedInfos.mapNotNull {
if (it in matchedExistedInfos) return@mapNotNull null
if (currentFlag in it.attributes) return@mapNotNull null
it.copy().apply {
if (otherFlag !in attributes) {
attributes += otherFlag
}
}
}
globalMetadataInfoHandler.addMetadataInfosForFile(file, newInfos)
override fun firstAttributeEnabled(module: TestModule): Boolean {
return module.languageVersionSettings.supportsFeature(LanguageFeature.NewInference)
}
}
@@ -16,8 +16,12 @@ import org.jetbrains.kotlin.fir.analysis.FirAnalyzerFacade
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
import org.jetbrains.kotlin.fir.session.FirJvmModuleInfo
import org.jetbrains.kotlin.fir.session.FirSessionFactory
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.model.*
import org.jetbrains.kotlin.test.services.*
import java.io.File
class FirFrontendFacade(
testServices: TestServices
@@ -25,6 +29,9 @@ class FirFrontendFacade(
override val additionalServices: List<ServiceRegistrationData>
get() = listOf(service(::FirModuleInfoProvider))
override val additionalDirectives: List<DirectivesContainer>
get() = listOf(FirDiagnosticsDirectives)
override fun analyze(module: TestModule): FirOutputArtifact {
val moduleInfoProvider = testServices.firModuleInfoProvider
val compilerConfigurationProvider = testServices.compilerConfigurationProvider
@@ -34,7 +41,12 @@ class FirFrontendFacade(
PsiElementFinder.EP.getPoint(project).unregisterExtension(JavaElementFinder::class.java)
val ktFiles = testServices.sourceFileProvider.getKtFilesForSourceFiles(module.files, project).values
val lightTreeEnabled = FirDiagnosticsDirectives.USE_LIGHT_TREE in module.directives
val (ktFiles, originalFiles) = if (lightTreeEnabled) {
emptyList<KtFile>() to module.files.filter { it.isKtFile }.map { testServices.sourceFileProvider.getRealFileForSourceFile(it) }
} else {
testServices.sourceFileProvider.getKtFilesForSourceFiles(module.files, project).values to emptyList()
}
val sessionProvider = moduleInfoProvider.firSessionProvider
@@ -55,7 +67,7 @@ class FirFrontendFacade(
languageVersionSettings = languageVersionSettings
)
val firAnalyzerFacade = FirAnalyzerFacade(session, languageVersionSettings, ktFiles)
val firAnalyzerFacade = FirAnalyzerFacade(session, languageVersionSettings, ktFiles, originalFiles, lightTreeEnabled)
val firFiles = firAnalyzerFacade.runResolution()
val filesMap = firFiles.mapNotNull { firFile ->
val testFile = module.files.firstOrNull { it.name == firFile.name } ?: return@mapNotNull null
@@ -32,11 +32,13 @@ import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives
import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.utils.AbstractTwoAttributesMetaInfoProcessor
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -62,6 +64,8 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
override fun processModule(module: TestModule, info: FirOutputArtifact) {
val diagnosticsPerFile = info.firAnalyzerFacade.runCheckers()
val lightTreeComparingModeEnabled = FirDiagnosticsDirectives.COMPARE_WITH_LIGHT_TREE in module.directives
val lightTreeEnabled = FirDiagnosticsDirectives.USE_LIGHT_TREE in module.directives
for (file in module.files) {
val firFile = info.firFiles[file] ?: continue
@@ -71,29 +75,42 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
// SYNTAX errors will be reported later
if (diagnostic.factory == FirErrors.SYNTAX) return@mapNotNull null
if (!diagnostic.isValid) return@mapNotNull null
diagnostic.toMetaInfo(file)
diagnostic.toMetaInfo(file, lightTreeEnabled, lightTreeComparingModeEnabled)
}
globalMetadataInfoHandler.addMetadataInfosForFile(file, diagnosticsMetadataInfos)
collectSyntaxDiagnostics(file, firFile)
collectDebugInfoDiagnostics(file, firFile)
collectSyntaxDiagnostics(file, firFile, lightTreeEnabled, lightTreeComparingModeEnabled)
collectDebugInfoDiagnostics(file, firFile, lightTreeEnabled, lightTreeComparingModeEnabled)
}
}
private fun FirDiagnostic<*>.toMetaInfo(file: TestFile, forceRenderArguments: Boolean = false): FirDiagnosticCodeMetaInfo {
private fun FirDiagnostic<*>.toMetaInfo(
file: TestFile,
lightTreeEnabled: Boolean,
lightTreeComparingModeEnabled: Boolean,
forceRenderArguments: Boolean = false
): FirDiagnosticCodeMetaInfo {
val metaInfo = FirDiagnosticCodeMetaInfo(this, FirMetaInfoUtils.renderDiagnosticNoArgs)
val shouldRenderArguments = forceRenderArguments || globalMetadataInfoHandler.getExistingMetaInfosForActualMetadata(file, metaInfo)
.any { it.description != null }
if (shouldRenderArguments) {
metaInfo.replaceRenderConfiguration(FirMetaInfoUtils.renderDiagnosticWithArgs)
}
if (lightTreeComparingModeEnabled) {
metaInfo.attributes += if (lightTreeEnabled) PsiLightTreeMetaInfoProcessor.LT else PsiLightTreeMetaInfoProcessor.PSI
}
return metaInfo
}
private fun collectSyntaxDiagnostics(testFile: TestFile, firFile: FirFile) {
private fun collectSyntaxDiagnostics(
testFile: TestFile,
firFile: FirFile,
lightTreeEnabled: Boolean,
lightTreeComparingModeEnabled: Boolean
) {
// TODO: support in light tree
val psiFile = firFile.psi ?: return
val metaInfos = AnalyzingUtils.getSyntaxErrorRanges(psiFile).map {
FirErrors.SYNTAX.on(FirRealPsiSourceElement(it)).toMetaInfo(testFile)
FirErrors.SYNTAX.on(FirRealPsiSourceElement(it)).toMetaInfo(testFile, lightTreeEnabled, lightTreeComparingModeEnabled)
}
globalMetadataInfoHandler.addMetadataInfosForFile(testFile, metaInfos)
}
@@ -101,6 +118,8 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
private fun collectDebugInfoDiagnostics(
testFile: TestFile,
firFile: FirFile,
lightTreeEnabled: Boolean,
lightTreeComparingModeEnabled: Boolean
) {
val result = mutableListOf<FirDiagnostic<*>>()
val diagnosedRangesToDiagnosticNames = globalMetadataInfoHandler.getExistingMetaInfosForFile(testFile).groupBy(
@@ -128,7 +147,10 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
super.visitFunctionCall(functionCall)
}
}.let(firFile::accept)
globalMetadataInfoHandler.addMetadataInfosForFile(testFile, result.map { it.toMetaInfo(testFile, forceRenderArguments = true) })
globalMetadataInfoHandler.addMetadataInfosForFile(
testFile,
result.map { it.toMetaInfo(testFile, lightTreeEnabled, lightTreeComparingModeEnabled, forceRenderArguments = true) }
)
}
fun createExpressionTypeDiagnosticIfExpected(
@@ -230,3 +252,21 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {}
}
class PsiLightTreeMetaInfoProcessor(testServices: TestServices) : AbstractTwoAttributesMetaInfoProcessor(testServices) {
companion object {
const val PSI = "PSI"
const val LT = "LT" // Light Tree
}
override val firstAttribute: String get() = PSI
override val secondAttribute: String get() = LT
override fun processorEnabled(module: TestModule): Boolean {
return FirDiagnosticsDirectives.COMPARE_WITH_LIGHT_TREE in module.directives
}
override fun firstAttributeEnabled(module: TestModule): Boolean {
return FirDiagnosticsDirectives.USE_LIGHT_TREE !in module.directives
}
}
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.test.generators
import org.jetbrains.kotlin.generators.util.TestGeneratorUtil
import org.jetbrains.kotlin.generators.util.TestGeneratorUtil.KT_WITHOUT_DOTS_IN_NAME
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.runners.*
@@ -47,8 +47,12 @@ fun main(args: Array<String>) {
testGroup("compiler/tests-common-new/tests-gen", "compiler/fir/analysis-tests/testData") {
testClass<AbstractFirDiagnosticTest> {
model("resolve", pattern = TestGeneratorUtil.KT_WITHOUT_DOTS_IN_NAME)
model("resolveWithStdlib", pattern = TestGeneratorUtil.KT_WITHOUT_DOTS_IN_NAME)
model("resolve", pattern = KT_WITHOUT_DOTS_IN_NAME)
model("resolveWithStdlib", pattern = KT_WITHOUT_DOTS_IN_NAME)
}
testClass<AbstractFirDiagnosticsWithLightTreeTest> {
model("resolve", pattern = KT_WITHOUT_DOTS_IN_NAME)
}
}
}
@@ -123,7 +123,12 @@ class TestConfigurationImpl(
init {
testServices.apply {
this@TestConfigurationImpl.facades.values.forEach { it.values.forEach { facade -> register(facade.additionalServices) } }
this@TestConfigurationImpl.facades.values.forEach {
it.values.forEach { facade ->
register(facade.additionalServices)
allDirectives += facade.additionalDirectives
}
}
}
}
@@ -8,7 +8,11 @@ package org.jetbrains.kotlin.test.runners
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives
import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.COMPARE_WITH_LIGHT_TREE
import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.FIR_DUMP
import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.USE_LIGHT_TREE
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.WITH_STDLIB
import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives
import org.jetbrains.kotlin.test.frontend.fir.FirFailingTestSuppressor
import org.jetbrains.kotlin.test.frontend.fir.FirFrontendFacade
@@ -45,6 +49,8 @@ abstract class AbstractFirDiagnosticTest : AbstractKotlinCompilerTest() {
::FirCfgConsistencyHandler,
)
useMetaInfoProcessors(::PsiLightTreeMetaInfoProcessor)
forTestsMatching("compiler/testData/diagnostics/*") {
useAfterAnalysisCheckers(
::FirIdenticalChecker,
@@ -55,7 +61,8 @@ abstract class AbstractFirDiagnosticTest : AbstractKotlinCompilerTest() {
forTestsMatching("compiler/fir/analysis-tests/testData/*") {
defaultDirectives {
+FirDiagnosticsDirectives.FIR_DUMP
+FIR_DUMP
+COMPARE_WITH_LIGHT_TREE
}
}
@@ -65,7 +72,18 @@ abstract class AbstractFirDiagnosticTest : AbstractKotlinCompilerTest() {
"compiler/testData/diagnostics/tests/unsignedTypes/*"
) {
defaultDirectives {
+JvmEnvironmentConfigurationDirectives.WITH_STDLIB
+WITH_STDLIB
}
}
}
}
abstract class AbstractFirDiagnosticsWithLightTreeTest : AbstractFirDiagnosticTest() {
override fun configure(builder: TestConfigurationBuilder) {
super.configure(builder)
with(builder) {
defaultDirectives {
+USE_LIGHT_TREE
}
}
}
@@ -0,0 +1,76 @@
/*
* 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.utils
import org.jetbrains.kotlin.codeMetaInfo.model.CodeMetaInfo
import org.jetbrains.kotlin.codeMetaInfo.model.ParsedCodeMetaInfo
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.AdditionalMetaInfoProcessor
import org.jetbrains.kotlin.test.services.TestServices
abstract class AbstractTwoAttributesMetaInfoProcessor(testServices: TestServices) : AdditionalMetaInfoProcessor(testServices) {
protected abstract val firstAttribute: String
protected abstract val secondAttribute: String
protected abstract fun processorEnabled(module: TestModule): Boolean
protected abstract fun firstAttributeEnabled(module: TestModule): Boolean
override fun processMetaInfos(module: TestModule, file: TestFile) {
/*
* Rules for OI/NI attribute:
* ┌──────────┬───────┬────────┬──────────┐
* │ │ first │ second │ nothing │ <- reported
* ├──────────┼───────┼────────┼──────────┤
* │ nothing │ both │ both │ nothing │
* │ first │ first │ both │ first │
* │ second │ both │ second │ second │
* │ both │ both │ both │ opposite │ <- first if second enabled in test and vice versa
* └──────────┴───────┴────────┴──────────┘
* ^ existed
*/
if (!processorEnabled(module)) return
val (currentFlag, otherFlag) = when (firstAttributeEnabled(module)) {
true -> firstAttribute to secondAttribute
false -> secondAttribute to firstAttribute
}
val matchedExistedInfos = mutableSetOf<ParsedCodeMetaInfo>()
val matchedReportedInfos = mutableSetOf<CodeMetaInfo>()
val allReportedInfos = globalMetadataInfoHandler.getReportedMetaInfosForFile(file)
for ((_, reportedInfos) in allReportedInfos.groupBy { Triple(it.start, it.end, it.tag) }) {
val existedInfos = globalMetadataInfoHandler.getExistingMetaInfosForActualMetadata(file, reportedInfos.first())
for ((reportedInfo, existedInfo) in reportedInfos.zip(existedInfos)) {
matchedExistedInfos += existedInfo
matchedReportedInfos += reportedInfo
if (currentFlag !in reportedInfo.attributes) continue
if (currentFlag in existedInfo.attributes) continue
reportedInfo.attributes.remove(currentFlag)
}
}
if (allReportedInfos.size != matchedReportedInfos.size) {
for (info in allReportedInfos) {
if (info !in matchedReportedInfos) {
info.attributes.remove(currentFlag)
}
}
}
val allExistedInfos = globalMetadataInfoHandler.getExistingMetaInfosForFile(file)
if (allExistedInfos.size == matchedExistedInfos.size) return
val newInfos = allExistedInfos.mapNotNull {
if (it in matchedExistedInfos) return@mapNotNull null
if (currentFlag in it.attributes) return@mapNotNull null
it.copy().apply {
if (otherFlag !in attributes) {
attributes += otherFlag
}
}
}
globalMetadataInfoHandler.addMetadataInfosForFile(file, newInfos)
}
}
@@ -603,14 +603,9 @@ fun main(args: Array<String>) {
}
testGroup("compiler/fir/analysis-tests/tests-gen", "compiler/fir/analysis-tests/testData") {
testClass<AbstractFirDiagnosticsWithLightTreeTest> {
model("resolve", pattern = KT_WITHOUT_DOTS_IN_NAME)
}
testClass<AbstractLazyBodyIsNotTouchedTilContractsPhaseTest> {
model("resolve", pattern = KT_WITHOUT_DOTS_IN_NAME)
}
}
testGroup("compiler/fir/analysis-tests/tests-gen", "compiler/testData") {