FIR IDE: introduce base class for multifile tests
This commit is contained in:
+88
@@ -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"
|
||||||
|
}
|
||||||
+57
@@ -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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+54
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user