FIR IDE: introduce base class for multifile tests

This commit is contained in:
Ilya Kirillov
2021-02-18 20:26:31 +01:00
parent 3626008ed2
commit 804df1aec2
4 changed files with 211 additions and 0 deletions
@@ -0,0 +1,88 @@
/*
* 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.idea.test.framework
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import java.io.File
abstract class AbstractKtIdeaTest : KotlinLightCodeInsightFixtureTestCase() {
protected open val allowedDirectives: List<TestFileDirective<*>> = emptyList()
protected fun doTest(path: String) {
val testDataFile = File(path)
val text = FileUtil.loadFile(testDataFile)
val testFileStructure = createTestFileStructure(text, testDataFile)
doTestByFileStructure(testFileStructure)
}
private fun createTestFileStructure(text: String, testDataFile: File): TestFileStructure {
val files = FileSplitter.splitIntoFiles(text, testDataFile.name)
val mainFile = createTestFile(files.first(), isMainFile = true)
val testFiles = files.drop(1).map { file ->
createTestFile(file, isMainFile = false)
}
val directives = parseDirectives(text)
return TestFileStructure(
filePath = testDataFile.toPath(),
caretPosition = getCaretPosition(text),
directives = directives,
mainFile = mainFile as TestFile.KtTestFile,
otherFiles = testFiles
)
}
private fun getCaretPosition(text: String) = text.indexOfOrNull(KtTest.CARET_SYMBOL)
private fun parseDirectives(text: String): TestFileDirectives {
val directives = text.lineSequence().mapNotNull(::extractDirectiveIfAny)
return TestFileDirectives(directives.toMap())
}
private fun extractDirectiveIfAny(line: String): Pair<String, Any>? {
val directive = allowedDirectives.firstOrNull { directive -> line.startsWith(directive.name) } ?: return null
val value = line.substringAfter(directive.name).trim()
val parsedValue = directive.parse(value)
?: error("Invalid ${directive.name} value `$value`")
return directive.name to parsedValue
}
private fun createTestFile(file: FileSplitter.FileNameWithText, isMainFile: Boolean): TestFile {
val psiFile = if (isMainFile) {
myFixture.configureByText(file.name, file.text)
} else {
myFixture.addFileToProject(file.name, file.text)
}
return TestFile.createByPsiFile(psiFile)
}
abstract fun doTestByFileStructure(fileStructure: TestFileStructure)
}
private object FileSplitter {
data class FileNameWithText(val name: String, val text: String)
fun splitIntoFiles(text: String, defaultName: String): List<FileNameWithText> {
val result = mutableListOf<FileNameWithText>()
val stopAt = text.indexOfOrNull(KtTest.RESULT_DIRECTIVE) ?: text.length
var index = text.indexOfOrNull(KtTest.FILE_DIRECTIVE) ?: return listOf(FileNameWithText(defaultName, text))
while (index < stopAt) {
val eolIndex = text.indexOfOrNull("\n", index) ?: text.length
val fileName = text.substring(index + KtTest.FILE_DIRECTIVE.length, eolIndex).trim()
val nextFileIndex = text.indexOfOrNull(KtTest.FILE_DIRECTIVE, index + 1) ?: stopAt
val fileText = text.substring(eolIndex, nextFileIndex).trim()
index = nextFileIndex
result += FileNameWithText(fileName, fileText)
}
return result
}
}
private fun String.indexOfOrNull(substring: String) =
indexOf(substring).takeIf { it >= 0 }
private fun String.indexOfOrNull(substring: String, startingIndex: Int) =
indexOf(substring, startingIndex).takeIf { it >= 0 }
@@ -0,0 +1,12 @@
/*
* 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.idea.test.framework
object KtTest {
const val CARET_SYMBOL = "<caret>"
const val FILE_DIRECTIVE = "// FILE:"
const val RESULT_DIRECTIVE = "// RESULT"
}
@@ -0,0 +1,57 @@
/*
* 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.idea.test.framework
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiJavaFile
import org.jetbrains.kotlin.psi.KtFile
import java.nio.file.Path
class TestFileStructure(
val filePath: Path,
val caretPosition: Int?,
val directives: TestFileDirectives,
val mainFile: TestFile.KtTestFile,
val otherFiles: List<TestFile>,
) {
val mainKtFile: KtFile
get() = mainFile.psiFile
val allFiles: List<TestFile> = listOf(mainFile) + otherFiles
}
data class TestStructureExpectedDataBlock(val name: String, val values: List<String>)
class TestFileDirectives(
private val directives: Map<String, Any>
) {
fun <VALUE : Any, DIRECTIVE : TestFileDirective<VALUE>> getDirectiveValueIfPresent(directive: DIRECTIVE): VALUE? {
val value = directives[directive.name] ?: return null
@Suppress("UNCHECKED_CAST")
return value as VALUE
}
}
abstract class TestFileDirective<VALUE : Any> {
abstract val name: String
abstract fun parse(value: String): VALUE?
}
sealed class TestFile {
abstract val psiFile: PsiFile
data class KtTestFile(override val psiFile: KtFile) : TestFile()
data class JavaTestFile(override val psiFile: PsiJavaFile) : TestFile()
companion object {
fun createByPsiFile(psiFile: PsiFile) = when (psiFile) {
is KtFile -> KtTestFile(psiFile)
is PsiJavaFile -> JavaTestFile(psiFile)
else -> error("Unknown file type ${psiFile::class}")
}
}
}
@@ -0,0 +1,54 @@
/*
* 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.idea.test.framework
object TestStructureRenderer {
fun render(testStructure: TestFileStructure, expectedData: List<TestStructureExpectedDataBlock>): String = buildString {
renderFiles(testStructure)
renderExpectedData(expectedData)
renderCaretSymbol(testStructure)
}
private fun StringBuilder.renderCaretSymbol(testStructure: TestFileStructure) {
testStructure.caretPosition?.let { position ->
insert(position, KtTest.CARET_SYMBOL)
}
}
private fun StringBuilder.renderFiles(testStructure: TestFileStructure) {
if (testStructure.otherFiles.isEmpty()) {
appendLine(testStructure.mainFile.psiFile.text)
} else {
testStructure.allFiles.forEach { file ->
renderFile(file)
}
}
}
private fun StringBuilder.renderExpectedData(expectedData: List<TestStructureExpectedDataBlock>) {
if (expectedData.isNotEmpty()) {
appendLine(KtTest.RESULT_DIRECTIVE)
appendLine()
expectedData.forEach { block ->
renderExpectedDataBlock(block)
appendLine()
}
}
}
private fun StringBuilder.renderExpectedDataBlock(block: TestStructureExpectedDataBlock) {
appendLine("// ${block.name}")
block.values.forEach { value ->
appendLine("// $value")
}
}
private fun StringBuilder.renderFile(file: TestFile) {
appendLine("${KtTest.FILE_DIRECTIVE} ${file.psiFile.name}")
appendLine(file.psiFile.text)
appendLine()
}
}