[LL FIR] KT-50732 Add support for LL FIR-specific tests (.ll.kt)
- `.ll.kt` test data can be added in cases where LL FIR resolution
legally diverges from K2 compiler results.
- Each `.ll.kt` test is prefixed with an `LL_FIR_DIVERGENCE` directive
which must explain why the test may diverge from K2 compiler results.
- `LLFirDivergenceCommentChecker` ensures that each `.ll.kt` file
contains an `LL_FIR_DIVERGENCE` directive.
- `LLFirIdenticalChecker` results in an assertion error if the `.ll.kt`
test and its base test are completely identical, including in their
meta info (but ignoring `LL_FIR_DIVERGENCE`).
- The checker additionally ensures that the base source file and the
`.ll.kt` source file have identical Kotlin source code (ignoring
meta info and `LL_FIR_DIVERGENCE`). This ensures that both tests
test the exact same thing.
- `.ll.kt` files are ignored by select test generators, in addition to
`.fir.kt` files.
This commit is contained in:
committed by
teamcity
parent
b0db0f59b4
commit
88ac5727cc
+4
@@ -50,6 +50,10 @@ abstract class AbstractCompilerBasedTestForFir : AbstractCompilerBasedTest() {
|
||||
firHandlersStep {
|
||||
useHandlers(::LLDiagnosticParameterChecker)
|
||||
}
|
||||
|
||||
useMetaTestConfigurators(::LLFirMetaTestConfigurator)
|
||||
useAfterAnalysisCheckers(::LLFirIdenticalChecker)
|
||||
useAfterAnalysisCheckers(::LLFirDivergenceCommentChecker)
|
||||
}
|
||||
|
||||
open fun TestConfigurationBuilder.configureTest() {}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.analysis.low.level.api.fir.compiler.based
|
||||
|
||||
import org.jetbrains.kotlin.test.WrappedException
|
||||
import org.jetbrains.kotlin.test.model.AfterAnalysisChecker
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
import org.jetbrains.kotlin.test.services.assertions
|
||||
import org.jetbrains.kotlin.test.services.moduleStructure
|
||||
import org.jetbrains.kotlin.test.utils.isLLFirTestData
|
||||
|
||||
class LLFirDivergenceCommentChecker(testServices: TestServices) : AfterAnalysisChecker(testServices) {
|
||||
override fun check(failedAssertions: List<WrappedException>) {
|
||||
val testDataFile = testServices.moduleStructure.originalTestDataFiles.first()
|
||||
if (!testDataFile.isLLFirTestData) return
|
||||
|
||||
if (!testDataFile.hasLlFirDivergenceDirective()) {
|
||||
testServices.assertions.fail {
|
||||
"""The LL FIR test data file `${testDataFile.name}` is missing an `LL_FIR_DIVERGENCE` directive. At the beginning of the
|
||||
|file, add the following directive:
|
||||
|
|
||||
|// LL_FIR_DIVERGENCE
|
||||
|// A comment describing why the LL FIR result is diverging from the compiler result. You must provide a good reason, or
|
||||
|// otherwise the divergence is probably a bug in LL FIR which needs to be fixed. Try to be as specific as possible.
|
||||
|// LL_FIR_DIVERGENCE
|
||||
|""".trimMargin()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.analysis.low.level.api.fir.compiler.based
|
||||
|
||||
import java.io.File
|
||||
|
||||
private const val LL_FIR_DIVERGENCE_DIRECTIVE = "LL_FIR_DIVERGENCE"
|
||||
private const val LL_FIR_DIVERGENCE_DIRECTIVE_COMMENT = "// $LL_FIR_DIVERGENCE_DIRECTIVE"
|
||||
|
||||
/**
|
||||
* Checks whether the [File] contains a legal `LL_FIR_DIVERGENCE` directive without reading the whole file.
|
||||
*/
|
||||
fun File.hasLlFirDivergenceDirective(): Boolean = useLines { findDirectiveInLines(it.iterator()) }
|
||||
|
||||
fun String.removeLlFirDivergenceDirective(trimLines: Boolean): String {
|
||||
// To ignore `LL_FIR_DIVERGENCE`, we advance `iterator` with `findDirectiveInLines` and then concatenate the rest of the lines.
|
||||
val iterator = this.lineSequence().iterator()
|
||||
return if (findDirectiveInLines(iterator)) {
|
||||
// `trimStart` ensures that the `LL_FIR_DIVERGENCE` directive can be separated from the rest of the file by blank lines.
|
||||
iterator.asSequence().concatLines(trimLines).trimStart()
|
||||
} else this
|
||||
}
|
||||
|
||||
private fun Sequence<String>.concatLines(trimLines: Boolean): String =
|
||||
if (trimLines) joinToString("\n") { it.trimEnd() }.trimEnd()
|
||||
else joinToString("\n")
|
||||
|
||||
/**
|
||||
* Tries to find the `LL_FIR_DIVERGENCE` directive in the lines given by [iterator] and returns whether this is the case. If the directive
|
||||
* was found, [iterator] is guaranteed to be advanced exactly past the `LL_FIR_DIVERGENCE` directive.
|
||||
*
|
||||
* The format of the directive is as such:
|
||||
*
|
||||
* ```
|
||||
* // LL_FIR_DIVERGENCE
|
||||
* // lorem ipsum
|
||||
* // dolor sit amet
|
||||
* // LL_FIR_DIVERGENCE
|
||||
* ```
|
||||
*
|
||||
* Blank lines before the directive or inside the directive region are ignored.
|
||||
*/
|
||||
private fun findDirectiveInLines(iterator: Iterator<String>): Boolean {
|
||||
val firstNonBlankLine = iterator.nextNonBlankLineTrimmed()
|
||||
if (firstNonBlankLine != LL_FIR_DIVERGENCE_DIRECTIVE_COMMENT) return false
|
||||
|
||||
// Ignore line comments and blank lines until the second (closing) `LL_FIR_DIVERGENCE` is found. Any other text, such as uncommented
|
||||
// code inside the directive region, is illegal.
|
||||
while (iterator.hasNext()) {
|
||||
val line = iterator.nextNonBlankLineTrimmed() ?: return false
|
||||
if (line.startsWith("//")) {
|
||||
if (line == LL_FIR_DIVERGENCE_DIRECTIVE_COMMENT) return true
|
||||
} else return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun Iterator<String>.nextNonBlankLineTrimmed(): String? {
|
||||
while (hasNext()) {
|
||||
val line = next().trimEnd()
|
||||
if (line.isNotEmpty()) return line
|
||||
}
|
||||
return null
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.analysis.low.level.api.fir.compiler.based
|
||||
|
||||
import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives
|
||||
import org.jetbrains.kotlin.test.frontend.fir.handlers.AbstractFirIdenticalChecker
|
||||
import org.jetbrains.kotlin.test.model.TestFile
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
import org.jetbrains.kotlin.test.services.assertions
|
||||
import org.jetbrains.kotlin.test.services.sourceFileProvider
|
||||
import org.jetbrains.kotlin.test.utils.isLLFirTestData
|
||||
import java.io.File
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* `.ll.kt` test data should not be identical to its base `.fir.kt`/`.kt` test data. If a base `.fir.kt` file does not exist, the base file
|
||||
* is the `.kt` file.
|
||||
*
|
||||
* As the `LL_FIR_DIVERGENCE` directive only exists in `.ll.kt` files, [LLFirIdenticalChecker] ignores this directive when comparing the
|
||||
* LL FIR file's content to the base file's content.
|
||||
*/
|
||||
class LLFirIdenticalChecker(testServices: TestServices) : AbstractFirIdenticalChecker(testServices) {
|
||||
override fun checkTestDataFile(testDataFile: File) {
|
||||
if (!testDataFile.isLLFirTestData) return
|
||||
|
||||
val originalFile = helper.getClassicFileToCompare(testDataFile)
|
||||
val baseFile = helper.getFirFileToCompare(originalFile).takeIf { it.exists() } ?: originalFile
|
||||
|
||||
// `readContentIgnoringLlFirDivergenceDirective` trims whitespace after the `LL_FIR_DIVERGENCE` directive to allow blank lines
|
||||
// after the directive. Hence, the base content's starting whitespace needs to be trimmed as well, otherwise file contents might
|
||||
// differ in their starting whitespace.
|
||||
val baseContent = helper.readContent(baseFile, trimLines = true).trimStart()
|
||||
val llContent = helper.readContent(testDataFile, trimLines = false).removeLlFirDivergenceDirective(trimLines = true)
|
||||
if (baseContent == llContent) {
|
||||
testServices.assertions.fail {
|
||||
"`${testDataFile.name}` and `${baseFile.name}` are identical. Remove `$testDataFile`."
|
||||
}
|
||||
} else {
|
||||
assertPreprocessedTestDataAreEqual(baseFile, baseContent, testDataFile, llContent) {
|
||||
"When ignoring diagnostics, the contents of `${baseFile.name}` (expected) and `${testDataFile.name}` (actual) are not" +
|
||||
" identical. `.ll.kt` test data may only differ from its base `.fir.kt` or `.kt` test data in the reported" +
|
||||
" diagnostics and the `LL_FIR_DIVERGENCE` directive. Update one of these test data files."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that [baseFile] and [llFile] have the same content after preprocessing (which removes diagnostics and other meta info). This
|
||||
* prevents situations where one test data changes, but changes to the other test data are forgotten.
|
||||
*
|
||||
* [llContent] should have its `LL_FIR_DIVERGENCE` directive already removed.
|
||||
*/
|
||||
private fun assertPreprocessedTestDataAreEqual(
|
||||
baseFile: File,
|
||||
baseContent: String,
|
||||
llFile: File,
|
||||
llContent: String,
|
||||
message: () -> String,
|
||||
) {
|
||||
val processedBaseContent = testServices.sourceFileProvider.getContentOfSourceFile(
|
||||
TestFile(
|
||||
baseFile.path,
|
||||
baseContent,
|
||||
baseFile,
|
||||
startLineNumberInOriginalFile = 0,
|
||||
isAdditional = false,
|
||||
RegisteredDirectives.Empty,
|
||||
)
|
||||
)
|
||||
val processedLlContent = testServices.sourceFileProvider.getContentOfSourceFile(
|
||||
TestFile(
|
||||
llFile.path,
|
||||
llContent,
|
||||
llFile,
|
||||
startLineNumberInOriginalFile = 0,
|
||||
isAdditional = false,
|
||||
RegisteredDirectives.Empty,
|
||||
)
|
||||
)
|
||||
assertEquals(processedBaseContent, processedLlContent, message())
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.analysis.low.level.api.fir.compiler.based
|
||||
|
||||
import org.jetbrains.kotlin.test.services.MetaTestConfigurator
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
import org.jetbrains.kotlin.test.utils.llFirTestDataFile
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Uses `.ll.kt` test data if available.
|
||||
*/
|
||||
class LLFirMetaTestConfigurator(testServices: TestServices) : MetaTestConfigurator(testServices) {
|
||||
override fun transformTestDataPath(testDataFileName: String): String {
|
||||
val llFirFile = File(testDataFileName).llFirTestDataFile
|
||||
return if (llFirFile.exists()) llFirFile.path else testDataFileName
|
||||
}
|
||||
}
|
||||
+380
-380
File diff suppressed because it is too large
Load Diff
+377
-377
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user