mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
GitOrigin-RevId: 2055bae4ddb461c2d4ae938be6e809dd3b8097eb
316 lines
9.2 KiB
Kotlin
316 lines
9.2 KiB
Kotlin
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
package com.intellij.diff
|
|
|
|
import com.intellij.diff.comparison.CancellationChecker
|
|
import com.intellij.diff.comparison.ComparisonManagerImpl
|
|
import com.intellij.diff.comparison.iterables.DiffIterableUtil
|
|
import com.intellij.diff.util.DiffUtil
|
|
import com.intellij.diff.util.ThreeSide
|
|
import com.intellij.openapi.editor.Document
|
|
import com.intellij.openapi.progress.DumbProgressIndicator
|
|
import com.intellij.openapi.progress.ProgressIndicator
|
|
import com.intellij.openapi.util.text.StringUtil
|
|
import com.intellij.openapi.vcs.LocalFilePath
|
|
import com.intellij.testFramework.UsefulTestCase
|
|
import com.intellij.util.text.CharSequenceSubSequence
|
|
import junit.framework.ComparisonFailure
|
|
import junit.framework.TestCase
|
|
import org.junit.Assert
|
|
import java.util.*
|
|
import java.util.concurrent.atomic.AtomicLong
|
|
|
|
abstract class DiffTestCase : TestCase() {
|
|
private val DEFAULT_CHAR_COUNT = 12
|
|
private val DEFAULT_CHAR_TABLE: List<String> = listOf("\n", "\n", "\t", " ", " ", ".", "<", "!")
|
|
|
|
val RNG: Random = Random()
|
|
private var gotSeedException = false
|
|
|
|
val CANCELLATION: CancellationChecker = CancellationChecker.EMPTY
|
|
val INDICATOR: ProgressIndicator = DumbProgressIndicator.INSTANCE
|
|
val MANAGER: ComparisonManagerImpl = ComparisonManagerImpl()
|
|
|
|
val chSmile = "\uD83D\uDE02"
|
|
val chGun = "\uD83D\uDD2B"
|
|
val chMan = "\uD83E\uDDD2"
|
|
val chFace = "\uD83E\uDD2B"
|
|
|
|
override fun setUp() {
|
|
super.setUp()
|
|
DiffIterableUtil.setVerifyEnabled(true)
|
|
}
|
|
|
|
override fun tearDown() {
|
|
try {
|
|
DiffIterableUtil.setVerifyEnabled(false)
|
|
}
|
|
finally {
|
|
super.tearDown()
|
|
}
|
|
}
|
|
|
|
fun getTestName() = UsefulTestCase.getTestName(name, true)
|
|
|
|
//
|
|
// Misc
|
|
//
|
|
|
|
fun getLineCount(document: Document): Int {
|
|
return DiffUtil.getLineCount(document)
|
|
}
|
|
|
|
fun createFilePath(path: String) = LocalFilePath(path, path.endsWith('/') || path.endsWith('\\'))
|
|
|
|
//
|
|
// AutoTests
|
|
//
|
|
|
|
fun doAutoTest(seed: Long, runs: Int, test: (DebugData) -> Unit) {
|
|
RNG.setSeed(seed)
|
|
|
|
var lastSeed: Long = -1
|
|
val debugData = DebugData()
|
|
|
|
for (i in 1..runs) {
|
|
if (i % 1000 == 0 && !UsefulTestCase.IS_UNDER_TEAMCITY) println("Completed $i runs")
|
|
|
|
try {
|
|
lastSeed = getCurrentSeed()
|
|
|
|
test(debugData)
|
|
debugData.reset()
|
|
}
|
|
catch (e: Throwable) {
|
|
println("Seed: $seed")
|
|
println("Runs: $runs")
|
|
println("I: $i")
|
|
println("Current seed: $lastSeed")
|
|
debugData.dump()
|
|
throw e
|
|
}
|
|
}
|
|
}
|
|
|
|
fun generateText(maxLength: Int, charCount: Int, predefinedChars: List<String>): String {
|
|
val length = RNG.nextInt(maxLength + 1)
|
|
val builder = StringBuilder(length)
|
|
|
|
for (i in 1..length) {
|
|
val rnd = RNG.nextInt(charCount)
|
|
val char = predefinedChars.getOrElse(rnd) { (rnd + 97).toChar() }
|
|
builder.append(char)
|
|
}
|
|
return builder.toString()
|
|
}
|
|
|
|
fun generateText(maxLength: Int, useHighSurrogates: Boolean = false): String {
|
|
var count = DEFAULT_CHAR_COUNT
|
|
var table = DEFAULT_CHAR_TABLE
|
|
if (useHighSurrogates) {
|
|
count += 4
|
|
table += listOf(chSmile, chFace, chGun, chMan)
|
|
}
|
|
return generateText(maxLength, count, table)
|
|
}
|
|
|
|
fun getCurrentSeed(): Long {
|
|
if (gotSeedException) return -1
|
|
try {
|
|
val seedField = RNG.javaClass.getDeclaredField("seed")
|
|
seedField.isAccessible = true
|
|
val seedFieldValue = seedField.get(RNG) as AtomicLong
|
|
return seedFieldValue.get() xor 0x5DEECE66DL
|
|
}
|
|
catch (e: Exception) {
|
|
gotSeedException = true
|
|
System.err.println("Can't get random seed: " + e.message)
|
|
return -1
|
|
}
|
|
}
|
|
|
|
class DebugData {
|
|
private val data: MutableList<Pair<String, Any>> = ArrayList()
|
|
|
|
fun put(key: String, value: Any) {
|
|
data.add(Pair(key, value))
|
|
}
|
|
|
|
fun reset() {
|
|
data.clear()
|
|
}
|
|
|
|
fun dump() {
|
|
data.forEach { println(it.first + ": " + it.second) }
|
|
}
|
|
}
|
|
|
|
//
|
|
// Helpers
|
|
//
|
|
|
|
open class Trio<out T>(val data1: T, val data2: T, val data3: T) {
|
|
companion object {
|
|
fun <V> from(f: (ThreeSide) -> V): Trio<V> = Trio(f(ThreeSide.LEFT), f(ThreeSide.BASE), f(ThreeSide.RIGHT))
|
|
}
|
|
|
|
fun <V> map(f: (T) -> V): Trio<V> = Trio(f(data1), f(data2), f(data3))
|
|
|
|
fun <V> map(f: (T, ThreeSide) -> V): Trio<V> = Trio(f(data1, ThreeSide.LEFT), f(data2, ThreeSide.BASE), f(data3, ThreeSide.RIGHT))
|
|
|
|
fun forEach(f: (T, ThreeSide) -> Unit) {
|
|
f(data1, ThreeSide.LEFT)
|
|
f(data2, ThreeSide.BASE)
|
|
f(data3, ThreeSide.RIGHT)
|
|
}
|
|
|
|
operator fun invoke(side: ThreeSide): T = side.select(data1, data2, data3) as T
|
|
|
|
override fun toString(): String {
|
|
return "($data1, $data2, $data3)"
|
|
}
|
|
|
|
override fun equals(other: Any?): Boolean {
|
|
return other is Trio<*> && other.data1 == data1 && other.data2 == data2 && other.data3 == data3
|
|
}
|
|
|
|
override fun hashCode(): Int {
|
|
var h = 0
|
|
if (data1 != null) h = h * 31 + data1.hashCode()
|
|
if (data2 != null) h = h * 31 + data2.hashCode()
|
|
if (data3 != null) h = h * 31 + data3.hashCode()
|
|
return h
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
//
|
|
// Assertions
|
|
//
|
|
|
|
fun assertTrue(actual: Boolean, message: String = "") {
|
|
assertTrue(message, actual)
|
|
}
|
|
|
|
fun assertFalse(actual: Boolean, message: String = "") {
|
|
assertFalse(message, actual)
|
|
}
|
|
|
|
fun assertEquals(expected: Any?, actual: Any?, message: String = "") {
|
|
assertEquals(message, expected, actual)
|
|
}
|
|
|
|
fun assertEquals(expected: CharSequence?, actual: CharSequence?, message: String = "") {
|
|
if (!StringUtil.equals(expected, actual)) throw ComparisonFailure(message, expected?.toString(), actual?.toString())
|
|
}
|
|
|
|
fun assertEqualsCharSequences(chunk1: CharSequence, chunk2: CharSequence, ignoreSpaces: Boolean, skipLastNewline: Boolean) {
|
|
if (skipLastNewline && !ignoreSpaces) {
|
|
assertTrue(StringUtil.equals(chunk1, chunk2) ||
|
|
StringUtil.equals(stripNewline(chunk1), chunk2) ||
|
|
StringUtil.equals(chunk1, stripNewline(chunk2)))
|
|
}
|
|
else {
|
|
assertTrue(isEqualsCharSequences(chunk1, chunk2, ignoreSpaces))
|
|
}
|
|
}
|
|
|
|
fun assertNotEqualsCharSequences(chunk1: CharSequence, chunk2: CharSequence, ignoreSpaces: Boolean, skipLastNewline: Boolean) {
|
|
if (skipLastNewline && !ignoreSpaces) {
|
|
assertTrue(!StringUtil.equals(chunk1, chunk2) ||
|
|
!StringUtil.equals(stripNewline(chunk1), chunk2) ||
|
|
!StringUtil.equals(chunk1, stripNewline(chunk2)))
|
|
}
|
|
else {
|
|
assertFalse(isEqualsCharSequences(chunk1, chunk2, ignoreSpaces))
|
|
}
|
|
}
|
|
|
|
fun isEqualsCharSequences(chunk1: CharSequence, chunk2: CharSequence, ignoreSpaces: Boolean): Boolean {
|
|
if (ignoreSpaces) {
|
|
return StringUtil.equalsIgnoreWhitespaces(chunk1, chunk2)
|
|
}
|
|
else {
|
|
return StringUtil.equals(chunk1, chunk2)
|
|
}
|
|
}
|
|
|
|
fun assertOrderedEquals(expected: Collection<*>, actual: Collection<*>, message: String = "") {
|
|
UsefulTestCase.assertOrderedEquals(message, actual, expected)
|
|
}
|
|
|
|
fun assertSetsEquals(expected: BitSet, actual: BitSet, message: String = "") {
|
|
val sb = StringBuilder(message)
|
|
sb.append(": \"")
|
|
for (i in 0..actual.length()) {
|
|
sb.append(if (actual[i]) '-' else ' ')
|
|
}
|
|
sb.append('"')
|
|
val fullMessage = sb.toString()
|
|
|
|
Assert.assertEquals(fullMessage, expected, actual)
|
|
}
|
|
|
|
//
|
|
// Parsing
|
|
//
|
|
|
|
fun textToReadableFormat(text: CharSequence?): String {
|
|
if (text == null) return "null"
|
|
return "\"" + text.toString().replace('\n', '*').replace('\t', '+') + "\""
|
|
}
|
|
|
|
fun parseSource(string: CharSequence): String = string.toString().replace('_', '\n')
|
|
|
|
fun parseMatching(matching: String, text: Document): BitSet {
|
|
val pattern = matching.filterNot { it == '.' }
|
|
assertEquals(pattern.length, text.charsSequence.length)
|
|
|
|
val set = BitSet()
|
|
pattern.forEachIndexed { i, c -> if (c != ' ') set.set(i) }
|
|
return set
|
|
}
|
|
|
|
fun parseLineMatching(matching: String, document: Document): BitSet {
|
|
return parseLineMatching(matching, document.charsSequence)
|
|
}
|
|
|
|
fun parseLineMatching(matching: String, text: CharSequence): BitSet {
|
|
assertEquals(matching.length, text.length)
|
|
|
|
val lines1 = matching.split('_', '*')
|
|
val lines2 = text.split('\n')
|
|
assertEquals(lines1.size, lines2.size)
|
|
for (i in lines1.indices) {
|
|
assertEquals(lines1[i].length, lines2[i].length, "line $i")
|
|
}
|
|
|
|
|
|
val set = BitSet()
|
|
|
|
var index = 0
|
|
var lineNumber = 0
|
|
while (index < matching.length) {
|
|
var end = matching.indexOfAny(listOf("_", "*"), index) + 1
|
|
if (end == 0) end = matching.length
|
|
|
|
val line = matching.subSequence(index, end)
|
|
if (line.find { it != ' ' && it != '_' } != null) {
|
|
assert(!line.contains(' '))
|
|
set.set(lineNumber)
|
|
}
|
|
lineNumber++
|
|
index = end
|
|
}
|
|
|
|
return set
|
|
}
|
|
|
|
|
|
private fun stripNewline(text: CharSequence): CharSequence? {
|
|
return when (StringUtil.endsWithChar(text, '\n')) {
|
|
true -> CharSequenceSubSequence(text, 0, text.length - 1)
|
|
false -> null
|
|
}
|
|
}
|
|
}
|
|
} |