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