[TEST] Introduce test-infrastructure-utils module and extract common test utilities here

This commit is contained in:
Dmitriy Novozhilov
2020-12-02 16:00:43 +03:00
parent 1c91b74ff0
commit c8f3a4802e
189 changed files with 791 additions and 630 deletions
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2020 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.codeMetaInfo
import org.jetbrains.kotlin.codeMetaInfo.model.ParsedCodeMetaInfo
object CodeMetaInfoParser {
val openingRegex = """(<!([^"]*?((".*?")(, ".*?")*?)?[^"]*?)!>)""".toRegex()
val closingRegex = """(<!>)""".toRegex()
val openingOrClosingRegex = """(${closingRegex.pattern}|${openingRegex.pattern})""".toRegex()
/*
* ([\S&&[^,(){}]]+) -- tag, allowing all non-space characters except bracers and curly bracers
* ([{](.*?)[}])? -- list of platforms
* (\("(.*?)"\))? -- arguments of meta info
* (, )? -- possible separator between different infos
*/
private val tagRegex = """([\S&&[^,(){}]]+)([{](.*?)[}])?(\("(.*?)"\))?(, )?""".toRegex()
fun getCodeMetaInfoFromText(renderedText: String): List<ParsedCodeMetaInfo> {
var text = renderedText
val openingMatchResults = ArrayDeque<MatchResult>()
val stackOfOpeningMatchResults = ArrayDeque<MatchResult>()
val closingMatchResults = mutableMapOf<MatchResult, MatchResult>()
val result = mutableListOf<ParsedCodeMetaInfo>()
while (true) {
var openingStartOffset = Int.MAX_VALUE
var closingStartOffset = Int.MAX_VALUE
val opening = openingRegex.find(text)
val closing = closingRegex.find(text)
if (opening == null && closing == null) break
if (opening != null)
openingStartOffset = opening.range.first
if (closing != null)
closingStartOffset = closing.range.first
text = if (openingStartOffset < closingStartOffset) {
requireNotNull(opening)
openingMatchResults.addLast(opening)
stackOfOpeningMatchResults.addLast(opening)
text.removeRange(openingStartOffset, opening.range.last + 1)
} else {
requireNotNull(closing)
closingMatchResults[stackOfOpeningMatchResults.removeLast()] = closing
text.removeRange(closingStartOffset, closing.range.last + 1)
}
}
if (openingMatchResults.size != closingMatchResults.size) {
error("Opening and closing tags counts are not equals")
}
while (!openingMatchResults.isEmpty()) {
val openingMatchResult = openingMatchResults.removeLast()
val closingMatchResult = closingMatchResults.getValue(openingMatchResult)
val allMetaInfos = openingMatchResult.groups[2]!!.value
tagRegex.findAll(allMetaInfos).map { it.groups }.forEach {
val tag = it[1]!!.value
val platforms = it[3]?.value?.split(";") ?: emptyList()
val description = it[5]?.value
result.add(
ParsedCodeMetaInfo(
openingMatchResult.range.first,
closingMatchResult.range.first,
platforms.toMutableList(),
tag,
description
)
)
}
}
return result
}
}
@@ -0,0 +1,108 @@
/*
* Copyright 2010-2020 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.codeMetaInfo
import com.intellij.util.containers.Stack
import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil
import org.jetbrains.kotlin.codeMetaInfo.model.CodeMetaInfo
import java.io.File
object CodeMetaInfoRenderer {
fun renderTagsToText(
codeMetaInfos: List<CodeMetaInfo>,
originalText: String
): StringBuilder {
return StringBuilder().apply {
renderTagsToText(this, codeMetaInfos, originalText)
}
}
fun renderTagsToText(
builder: StringBuilder,
codeMetaInfos: List<CodeMetaInfo>,
originalText: String
) {
if (codeMetaInfos.isEmpty()) {
builder.append(originalText)
return
}
val sortedMetaInfos = getSortedCodeMetaInfos(codeMetaInfos).groupBy { it.start }
val opened = Stack<CodeMetaInfo>()
for ((i, c) in originalText.withIndex()) {
processMetaInfosStartedAtOffset(i, sortedMetaInfos, opened, builder)
builder.append(c)
}
val lastSymbolIsNewLine = builder.last() == '\n'
if (lastSymbolIsNewLine) {
builder.deleteCharAt(builder.length - 1)
}
processMetaInfosStartedAtOffset(originalText.length, sortedMetaInfos, opened, builder)
if (lastSymbolIsNewLine) {
builder.appendLine()
}
}
private fun processMetaInfosStartedAtOffset(
offset: Int,
sortedMetaInfos: Map<Int, List<CodeMetaInfo>>,
opened: Stack<CodeMetaInfo>,
builder: StringBuilder
) {
checkOpenedAndCloseStringIfNeeded(opened, offset, builder)
val matchedCodeMetaInfos = sortedMetaInfos[offset] ?: emptyList()
if (matchedCodeMetaInfos.isNotEmpty()) {
openStartTag(builder)
val iterator = matchedCodeMetaInfos.listIterator()
var current: CodeMetaInfo? = iterator.next()
while (current != null) {
val next: CodeMetaInfo? = if (iterator.hasNext()) iterator.next() else null
opened.push(current)
builder.append(current.asString())
when {
next == null ->
closeStartTag(builder)
next.end == current.end ->
builder.append(", ")
else ->
closeStartAndOpenNewTag(builder)
}
current = next
}
}
// Here we need to handle meta infos which has start == end and close them immediately
checkOpenedAndCloseStringIfNeeded(opened, offset, builder)
}
private val metaInfoComparator = (compareBy<CodeMetaInfo> { it.start } then compareByDescending { it.end }) then compareBy { it.tag }
private fun getSortedCodeMetaInfos(metaInfos: Collection<CodeMetaInfo>): List<CodeMetaInfo> {
return metaInfos.sortedWith(metaInfoComparator)
}
private fun closeString(result: StringBuilder) = result.append("<!>")
private fun openStartTag(result: StringBuilder) = result.append("<!")
private fun closeStartTag(result: StringBuilder) = result.append("!>")
private fun closeStartAndOpenNewTag(result: StringBuilder) = result.append("!><!")
private fun checkOpenedAndCloseStringIfNeeded(opened: Stack<CodeMetaInfo>, end: Int, result: StringBuilder) {
var prev: CodeMetaInfo? = null
while (!opened.isEmpty() && end == opened.peek().end) {
if (prev == null || prev.start != opened.peek().start)
closeString(result)
prev = opened.pop()
}
}
}
fun clearFileFromDiagnosticMarkup(file: File) {
val text = file.readText()
val cleanText = clearTextFromDiagnosticMarkup(text)
file.writeText(cleanText)
}
fun clearTextFromDiagnosticMarkup(text: String): String = text.replace(CodeMetaInfoParser.openingOrClosingRegex, "")
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2020 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.codeMetaInfo.model
import org.jetbrains.kotlin.codeMetaInfo.renderConfigurations.AbstractCodeMetaInfoRenderConfiguration
interface CodeMetaInfo {
val start: Int
val end: Int
val tag: String
val renderConfiguration: AbstractCodeMetaInfoRenderConfiguration
val attributes: MutableList<String>
fun asString(): String
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2020 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.codeMetaInfo.model
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.codeMetaInfo.renderConfigurations.DiagnosticCodeMetaInfoRenderConfiguration
import org.jetbrains.kotlin.diagnostics.Diagnostic
class DiagnosticCodeMetaInfo(
override val start: Int,
override val end: Int,
renderConfiguration: DiagnosticCodeMetaInfoRenderConfiguration,
val diagnostic: Diagnostic
) : CodeMetaInfo {
constructor(
range: TextRange,
renderConfiguration: DiagnosticCodeMetaInfoRenderConfiguration,
diagnostic: Diagnostic
) : this(range.startOffset, range.endOffset, renderConfiguration, diagnostic)
override var renderConfiguration: DiagnosticCodeMetaInfoRenderConfiguration = renderConfiguration
private set
fun replaceRenderConfiguration(renderConfiguration: DiagnosticCodeMetaInfoRenderConfiguration) {
this.renderConfiguration = renderConfiguration
}
override val tag: String
get() = renderConfiguration.getTag(this)
override val attributes: MutableList<String> = mutableListOf()
override fun asString(): String = renderConfiguration.asString(this)
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2020 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.codeMetaInfo.model
import org.jetbrains.kotlin.codeMetaInfo.renderConfigurations.ParsedCodeMetaInfoRenderConfiguration
class ParsedCodeMetaInfo(
override val start: Int,
override val end: Int,
override val attributes: MutableList<String>,
override val tag: String,
val description: String?
) : CodeMetaInfo {
override val renderConfiguration = ParsedCodeMetaInfoRenderConfiguration
override fun asString(): String = renderConfiguration.asString(this)
override fun equals(other: Any?): Boolean {
if (other == null || other !is CodeMetaInfo) return false
return this.tag == other.tag && this.start == other.start && this.end == other.end
}
override fun hashCode(): Int {
var result = start
result = 31 * result + end
result = 31 * result + tag.hashCode()
return result
}
fun copy(): ParsedCodeMetaInfo {
return ParsedCodeMetaInfo(start, end, attributes.toMutableList(), tag, description)
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2020 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.codeMetaInfo.renderConfigurations
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.codeMetaInfo.model.CodeMetaInfo
abstract class AbstractCodeMetaInfoRenderConfiguration(var renderParams: Boolean = true) {
private val clickOrPressRegex = "Click or press (.*)to navigate".toRegex() // We have different hotkeys on different platforms
open fun asString(codeMetaInfo: CodeMetaInfo): String = codeMetaInfo.tag + getPlatformsString(codeMetaInfo)
open fun getAdditionalParams(codeMetaInfo: CodeMetaInfo) = ""
protected fun sanitizeLineMarkerTooltip(originalText: String?): String {
if (originalText == null) return "null"
val noHtmlTags = StringUtil.removeHtmlTags(originalText)
.replace(" ", "")
.replace(clickOrPressRegex, "")
.trim()
return sanitizeLineBreaks(noHtmlTags)
}
protected fun sanitizeLineBreaks(originalText: String): String {
var sanitizedText = originalText
sanitizedText = StringUtil.replace(sanitizedText, "\r\n", " ")
sanitizedText = StringUtil.replace(sanitizedText, "\n", " ")
sanitizedText = StringUtil.replace(sanitizedText, "\r", " ")
return sanitizedText
}
protected fun getPlatformsString(codeMetaInfo: CodeMetaInfo): String {
if (codeMetaInfo.attributes.isEmpty()) return ""
return "{${codeMetaInfo.attributes.joinToString(";")}}"
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2020 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.codeMetaInfo.renderConfigurations
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory1
import org.jetbrains.kotlin.codeMetaInfo.model.CodeMetaInfo
import org.jetbrains.kotlin.codeMetaInfo.model.DiagnosticCodeMetaInfo
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.rendering.*
open class DiagnosticCodeMetaInfoRenderConfiguration(
val withNewInference: Boolean = true,
val renderSeverity: Boolean = false
) : AbstractCodeMetaInfoRenderConfiguration() {
private val crossPlatformLineBreak = """\r?\n""".toRegex()
override fun asString(codeMetaInfo: CodeMetaInfo): String {
if (codeMetaInfo !is DiagnosticCodeMetaInfo) return ""
return (getTag(codeMetaInfo)
+ getPlatformsString(codeMetaInfo)
+ getParamsString(codeMetaInfo))
.replace(crossPlatformLineBreak, "")
}
private fun getParamsString(codeMetaInfo: DiagnosticCodeMetaInfo): String {
if (!renderParams) return ""
val params = mutableListOf<String>()
@Suppress("UNCHECKED_CAST")
val renderer = when (codeMetaInfo.diagnostic.factory) {
is DebugInfoDiagnosticFactory1 -> DiagnosticWithParameters1Renderer(
"{0}",
Renderers.TO_STRING
) as DiagnosticRenderer<Diagnostic>
else -> DefaultErrorMessages.getRendererForDiagnostic(codeMetaInfo.diagnostic)
}
if (renderer is AbstractDiagnosticWithParametersRenderer) {
val renderParameters = renderer.renderParameters(codeMetaInfo.diagnostic)
params.addAll(ContainerUtil.map(renderParameters) { it.toString() })
}
if (renderSeverity)
params.add("severity='${codeMetaInfo.diagnostic.severity}'")
params.add(getAdditionalParams(codeMetaInfo))
return "(\"${params.filter { it.isNotEmpty() }.joinToString("; ")}\")"
}
fun getTag(codeMetaInfo: DiagnosticCodeMetaInfo): String {
return codeMetaInfo.diagnostic.factory.name
}
}
@@ -0,0 +1,16 @@
/*
* Copyright 2010-2020 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.codeMetaInfo.renderConfigurations
import org.jetbrains.kotlin.codeMetaInfo.model.CodeMetaInfo
import org.jetbrains.kotlin.codeMetaInfo.model.ParsedCodeMetaInfo
object ParsedCodeMetaInfoRenderConfiguration : AbstractCodeMetaInfoRenderConfiguration() {
override fun asString(codeMetaInfo: CodeMetaInfo): String {
require(codeMetaInfo is ParsedCodeMetaInfo)
return super.asString(codeMetaInfo) + (codeMetaInfo.description?.let { "(\"$it\")" } ?: "")
}
}