[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 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> { fun getCodeMetaInfoFromText(renderedText: String): List<ParsedCodeMetaInfo> {
var text = renderedText var text = renderedText
val openingMatchResults = ArrayDeque<MatchResult>()
val stackOfOpeningMatchResults = ArrayDeque<MatchResult>() val openings = ArrayDeque<Opening>()
val closingMatchResults = mutableMapOf<MatchResult, MatchResult>() val stackOfOpenings = ArrayDeque<Opening>()
val closingOffsets = mutableMapOf<Opening, Int>()
val result = mutableListOf<ParsedCodeMetaInfo>() val result = mutableListOf<ParsedCodeMetaInfo>()
var counter = 0
while (true) { while (true) {
var openingStartOffset = Int.MAX_VALUE var openingStartOffset = Int.MAX_VALUE
var closingStartOffset = Int.MAX_VALUE var closingStartOffset = Int.MAX_VALUE
@@ -42,22 +62,23 @@ object CodeMetaInfoParser {
text = if (openingStartOffset < closingStartOffset) { text = if (openingStartOffset < closingStartOffset) {
requireNotNull(opening) requireNotNull(opening)
openingMatchResults.addLast(opening) val openingMatch = Opening(counter++, opening.groups[2]!!.value, opening.range.first)
stackOfOpeningMatchResults.addLast(opening) openings.addLast(openingMatch)
stackOfOpenings.addLast(openingMatch)
text.removeRange(openingStartOffset, opening.range.last + 1) text.removeRange(openingStartOffset, opening.range.last + 1)
} else { } else {
requireNotNull(closing) requireNotNull(closing)
closingMatchResults[stackOfOpeningMatchResults.removeLast()] = closing closingOffsets[stackOfOpenings.removeLast()] = closing.range.first
text.removeRange(closingStartOffset, closing.range.last + 1) 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") error("Opening and closing tags counts are not equals")
} }
while (!openingMatchResults.isEmpty()) { while (!openings.isEmpty()) {
val openingMatchResult = openingMatchResults.removeLast() val openingMatchResult = openings.removeLast()
val closingMatchResult = closingMatchResults.getValue(openingMatchResult) val closingMatchResult = closingOffsets.getValue(openingMatchResult)
val allMetaInfos = openingMatchResult.groups[2]!!.value val allMetaInfos = openingMatchResult.tags
tagRegex.findAll(allMetaInfos).map { it.groups }.forEach { tagRegex.findAll(allMetaInfos).map { it.groups }.forEach {
val tag = it[1]!!.value val tag = it[1]!!.value
val attributes = it[3]?.value?.split(";") ?: emptyList() val attributes = it[3]?.value?.split(";") ?: emptyList()
@@ -65,8 +86,8 @@ object CodeMetaInfoParser {
result.add( result.add(
ParsedCodeMetaInfo( ParsedCodeMetaInfo(
openingMatchResult.range.first, openingMatchResult.startOffset,
closingMatchResult.range.first, closingMatchResult,
attributes.toMutableList(), attributes.toMutableList(),
tag, tag,
description description