[Test] Fix OutOfMemoryError during extracting meta info from test

Previously CodeMetaInfoParser saved MatchResults for each meta info in
  text (and for each meta info it was different string with text because
  of extracting parsed part from original text), so it led to O(n^2)
  memory complexity (where n is a size of original text).
  Such complexity caused OOME in case of big testdata (~1k meta infos
  for text of 4k lines)
This commit is contained in:
Dmitriy Novozhilov
2021-01-25 11:52:42 +03:00
parent fd8795de99
commit 0b45139b34
@@ -21,13 +21,33 @@ object CodeMetaInfoParser {
*/
private val tagRegex = """([\S&&[^,(){}]]+)([{](.*?)[}])?(\("(.*?)"\))?(, )?""".toRegex()
private class Opening(val index: Int, val tags: String, val startOffset: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Opening
if (index != other.index) return false
return true
}
override fun hashCode(): Int {
return index
}
}
fun getCodeMetaInfoFromText(renderedText: String): List<ParsedCodeMetaInfo> {
var text = renderedText
val openingMatchResults = ArrayDeque<MatchResult>()
val stackOfOpeningMatchResults = ArrayDeque<MatchResult>()
val closingMatchResults = mutableMapOf<MatchResult, MatchResult>()
val openings = ArrayDeque<Opening>()
val stackOfOpenings = ArrayDeque<Opening>()
val closingOffsets = mutableMapOf<Opening, Int>()
val result = mutableListOf<ParsedCodeMetaInfo>()
var counter = 0
while (true) {
var openingStartOffset = Int.MAX_VALUE
var closingStartOffset = Int.MAX_VALUE
@@ -42,22 +62,23 @@ object CodeMetaInfoParser {
text = if (openingStartOffset < closingStartOffset) {
requireNotNull(opening)
openingMatchResults.addLast(opening)
stackOfOpeningMatchResults.addLast(opening)
val openingMatch = Opening(counter++, opening.groups[2]!!.value, opening.range.first)
openings.addLast(openingMatch)
stackOfOpenings.addLast(openingMatch)
text.removeRange(openingStartOffset, opening.range.last + 1)
} else {
requireNotNull(closing)
closingMatchResults[stackOfOpeningMatchResults.removeLast()] = closing
closingOffsets[stackOfOpenings.removeLast()] = closing.range.first
text.removeRange(closingStartOffset, closing.range.last + 1)
}
}
if (openingMatchResults.size != closingMatchResults.size) {
if (openings.size != closingOffsets.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
while (!openings.isEmpty()) {
val openingMatchResult = openings.removeLast()
val closingMatchResult = closingOffsets.getValue(openingMatchResult)
val allMetaInfos = openingMatchResult.tags
tagRegex.findAll(allMetaInfos).map { it.groups }.forEach {
val tag = it[1]!!.value
val attributes = it[3]?.value?.split(";") ?: emptyList()
@@ -65,8 +86,8 @@ object CodeMetaInfoParser {
result.add(
ParsedCodeMetaInfo(
openingMatchResult.range.first,
closingMatchResult.range.first,
openingMatchResult.startOffset,
closingMatchResult,
attributes.toMutableList(),
tag,
description