[FIR-TEST] Add validation of diagnostics for light tree tests

This commit is contained in:
Dmitriy Novozhilov
2020-03-23 20:14:25 +03:00
parent 86e7faf639
commit 84ed3f5cf2
2 changed files with 98 additions and 7 deletions
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir
import junit.framework.TestCase
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector
import org.jetbrains.kotlin.fir.analysis.collectors.FirDiagnosticsCollector
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
import org.jetbrains.kotlin.fir.resolve.dfa.FirControlFlowGraphReferenceImpl
@@ -77,16 +78,13 @@ abstract class AbstractFirDiagnosticsTest : AbstractFirBaseDiagnosticsTest() {
)
}
protected fun checkDiagnostics(file: File, testFiles: List<TestFile>, firFiles: List<FirFile>) {
val collectors = mutableMapOf<FirSession, AbstractDiagnosticCollector>()
protected open fun checkDiagnostics(file: File, testFiles: List<TestFile>, firFiles: List<FirFile>) {
val diagnostics = collectDiagnostics(firFiles)
val actualText = StringBuilder()
for (testFile in testFiles) {
val firFile = firFiles.firstOrNull { it.psi == testFile.ktFile }
if (firFile != null) {
val session = firFile.session
val collector = collectors.computeIfAbsent(session) { createCollector(session) }
val firDiagnostics = collector.collectDiagnostics(firFile)
testFile.getActualText(firDiagnostics, actualText)
testFile.getActualText(diagnostics.getValue(firFile), actualText)
} else {
actualText.append(testFile.expectedText)
}
@@ -94,7 +92,18 @@ abstract class AbstractFirDiagnosticsTest : AbstractFirBaseDiagnosticsTest() {
KotlinTestUtils.assertEqualsToFile(file, actualText.toString())
}
protected fun createCollector(session: FirSession): AbstractDiagnosticCollector {
protected fun collectDiagnostics(firFiles: List<FirFile>): Map<FirFile, List<FirDiagnostic<*>>> {
val collectors = mutableMapOf<FirSession, AbstractDiagnosticCollector>()
val result = mutableMapOf<FirFile, List<FirDiagnostic<*>>>()
for (firFile in firFiles) {
val session = firFile.session
val collector = collectors.computeIfAbsent(session) { createCollector(session) }
result[firFile] = collector.collectDiagnostics(firFile).toList()
}
return result
}
private fun createCollector(session: FirSession): AbstractDiagnosticCollector {
return FirDiagnosticsCollector.create(session)
}
@@ -5,9 +5,15 @@
package org.jetbrains.kotlin.fir
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirLightDiagnostic
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
import kotlin.math.abs
abstract class AbstractFirDiagnosticsWithLightTreeTest : AbstractFirDiagnosticsTest() {
override fun doTest(filePath: String) {
@@ -20,4 +26,80 @@ abstract class AbstractFirDiagnosticsWithLightTreeTest : AbstractFirDiagnosticsT
override val useLightTree: Boolean
get() = true
override fun checkDiagnostics(file: File, testFiles: List<TestFile>, firFiles: List<FirFile>) {
val fileToDiagnostics = collectDiagnostics(firFiles)
val missingDiagnostics = mutableListOf<MissingDiagnostic>()
for (testFile in testFiles) {
val firFile = firFiles.firstOrNull { it.name == testFile.name } ?: continue
val ktFile = testFile.ktFile!!
val diagnostics = fileToDiagnostics[firFile] ?: emptyList()
val actualDiagnostics = diagnostics.groupBy {
require(it is FirLightDiagnostic<*>)
it.element.startOffset
}.mapValues { (_, diagnostics) -> diagnostics.map { it.factory.name }.countEntries() }
val existingDiagnostics = testFile.diagnosedRanges.groupBy {
it.start
}.mapValues { (_, ranges) -> ranges.flatMap { it.getDiagnostics().map { it.name } }.countEntries() }
for (startOffset in actualDiagnostics.keys + existingDiagnostics.keys) {
val expected = existingDiagnostics[startOffset] ?: emptyMap()
val actual = actualDiagnostics[startOffset] ?: emptyMap()
for (name in expected.keys + actual.keys) {
val expectedCount = expected[name] ?: 0
val actualCount = actual[name] ?: 0
if (expectedCount != actualCount) {
missingDiagnostics += MissingDiagnostic(startOffset, name, expectedCount - actualCount, ktFile)
}
}
}
if (missingDiagnostics.isNotEmpty()) {
val wasExpected = missingDiagnostics.filter { it.kind == MissingDiagnostic.Kind.WasExpected }
val isActual = missingDiagnostics.filter { it.kind == MissingDiagnostic.Kind.IsActual }
if (wasExpected.sumBy { it.count } == isActual.sumBy { it.count }) return
val message = buildString {
fun MissingDiagnostic.errorMessage(): String {
val position = DiagnosticUtils.getLineAndColumnInPsiFile(ktFile, TextRange(startOffset, startOffset + 1))
// I don't know why but somehow line is greater for 3 than line in real file
return " $count of $name at ${position.line - 3}:${position.column}"
}
if (wasExpected.isNotEmpty()) {
appendln("Some diagnostics was expected:")
wasExpected.forEach {
appendln(it.errorMessage())
}
}
if (isActual.isNotEmpty()) {
appendln("Some new diagnostics:")
isActual.forEach {
appendln(it.errorMessage())
}
}
}
kotlin.test.fail(message)
}
}
}
private fun <T> List<T>.countEntries(): Map<T, Int> {
return groupBy { it }.mapValues { (_, value) -> value.size }
}
private class MissingDiagnostic(val startOffset: Int, val name: String, diff: Int, ktFile: KtFile) {
val kind: Kind = if (diff > 0) Kind.WasExpected else Kind.IsActual
val count: Int = abs(diff)
enum class Kind {
WasExpected,
IsActual
}
}
}