mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
merge: tests
This commit is contained in:
@@ -107,6 +107,11 @@ public class TextDiffTypeFactory {
|
||||
public Color getMarkerColor(@Nullable Editor editor) {
|
||||
return getAttributes(editor).getErrorStripeColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myName;
|
||||
}
|
||||
}
|
||||
|
||||
private static final double MIDDLE_COLOR_FACTOR = 0.6;
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
package com.intellij.diff
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import junit.framework.ComparisonFailure
|
||||
import junit.framework.TestCase
|
||||
|
||||
public fun assertTrue(actual: Boolean, message: String = "") {
|
||||
@@ -30,6 +32,10 @@ public fun assertEquals(expected: Any?, actual: Any?, message: String = "") {
|
||||
TestCase.assertEquals(message, expected, actual)
|
||||
}
|
||||
|
||||
public fun assertEquals(expected: CharSequence?, actual: CharSequence?, message: String = "") {
|
||||
if (!StringUtil.equals(expected, actual)) throw ComparisonFailure(message, expected?.toString(), actual?.toString())
|
||||
}
|
||||
|
||||
public fun assertEmpty(collection: Collection<*>, message: String = "") {
|
||||
UsefulTestCase.assertEmpty(message, collection)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.diff
|
||||
|
||||
import com.intellij.diff.comparison.ComparisonManagerImpl
|
||||
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.Couple
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import com.intellij.util.containers.HashMap
|
||||
import com.intellij.util.text.CharSequenceSubSequence
|
||||
import java.util.ArrayList
|
||||
import java.util.BitSet
|
||||
import java.util.Random
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
public abstract class DiffTestCase : UsefulTestCase() {
|
||||
companion object {
|
||||
private val REGISTRY = Registry.get("diff.verify.iterable")
|
||||
|
||||
private val DEFAULT_CHAR_COUNT = 12
|
||||
private val DEFAULT_CHAR_TABLE: Map<Int, Char> = {
|
||||
val map = HashMap<Int, Char>()
|
||||
listOf('\n', '\n', '\t', ' ', ' ', '.', '<', '!').forEachIndexed { i, c -> map.put(i, c) }
|
||||
map
|
||||
}()
|
||||
}
|
||||
|
||||
public val RNG: Random = Random()
|
||||
private var gotSeedException = false
|
||||
|
||||
private var oldRegistryValue: Boolean = false
|
||||
|
||||
public val INDICATOR: ProgressIndicator = DumbProgressIndicator.INSTANCE
|
||||
public val MANAGER: ComparisonManagerImpl = ComparisonManagerImpl()
|
||||
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
oldRegistryValue = REGISTRY.asBoolean()
|
||||
REGISTRY.setValue(true)
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
REGISTRY.setValue(oldRegistryValue)
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
|
||||
public fun assertEqualsCharSequences(chunk1: CharSequence, chunk2: CharSequence, ignoreSpaces: Boolean, skipLastNewline: Boolean) {
|
||||
if (ignoreSpaces) {
|
||||
assertTrue(StringUtil.equalsIgnoreWhitespaces(chunk1, chunk2))
|
||||
} else {
|
||||
if (skipLastNewline) {
|
||||
if (StringUtil.equals(chunk1, chunk2)) return
|
||||
if (StringUtil.equals(stripNewline(chunk1), chunk2)) return
|
||||
if (StringUtil.equals(chunk1, stripNewline(chunk2))) return
|
||||
assertTrue(false)
|
||||
} else {
|
||||
assertTrue(StringUtil.equals(chunk1, chunk2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Parsing
|
||||
//
|
||||
|
||||
public fun textToReadableFormat(text: CharSequence?): String {
|
||||
if (text == null) return "null"
|
||||
return "'" + text.toString().replace('\n', '*').replace('\t', '+') + "'"
|
||||
}
|
||||
|
||||
public fun parseSource(string: CharSequence): String = string.toString().replace('_', '\n')
|
||||
|
||||
public fun parseMatching(before: String, after: String): Couple<BitSet> {
|
||||
return Couple.of(parseMatching(before), parseMatching(after))
|
||||
}
|
||||
|
||||
public fun parseMatching(matching: String): BitSet {
|
||||
val set = BitSet()
|
||||
matching.filterNot { it == '.' }.forEachIndexed { i, c -> if (c != ' ') set.set(i) }
|
||||
return set
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
//
|
||||
|
||||
public fun getLineCount(document: Document): Int {
|
||||
return Math.max(1, document.getLineCount())
|
||||
}
|
||||
|
||||
public fun Int.until(a: Int): IntRange = this..a - 1
|
||||
|
||||
//
|
||||
// AutoTests
|
||||
//
|
||||
|
||||
public 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) println(i)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun generateText(maxLength: Int, charCount: Int, predefinedChars: Map<Int, Char>): String {
|
||||
val length = RNG.nextInt(maxLength + 1)
|
||||
val builder = StringBuilder(length)
|
||||
|
||||
for (i in 1..length) {
|
||||
val rnd = RNG.nextInt(charCount)
|
||||
val char = predefinedChars.get(rnd) ?: (rnd + 97).toChar()
|
||||
builder.append(char)
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
public fun generateText(maxLength: Int): String {
|
||||
return generateText(maxLength, DEFAULT_CHAR_COUNT, DEFAULT_CHAR_TABLE)
|
||||
}
|
||||
|
||||
public fun getCurrentSeed(): Long {
|
||||
if (gotSeedException) return -1
|
||||
try {
|
||||
val seedField = RNG.javaClass.getDeclaredField("seed")
|
||||
seedField.setAccessible(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.getMessage())
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
private fun stripNewline(text: CharSequence): CharSequence? {
|
||||
return when (StringUtil.endsWithChar(text, '\n') ) {
|
||||
true -> CharSequenceSubSequence(text, 0, text.length() - 1)
|
||||
false -> null
|
||||
}
|
||||
}
|
||||
|
||||
public class DebugData() {
|
||||
private val data: MutableList<Pair<String, Any>> = ArrayList<Pair<String, Any>>()
|
||||
|
||||
public fun put(key: String, value: Any) {
|
||||
data.add(Pair(key, value))
|
||||
}
|
||||
|
||||
public fun reset() {
|
||||
data.clear()
|
||||
}
|
||||
|
||||
public fun dump() {
|
||||
data.forEach { println(it.first + ": " + it.second) }
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
public data open class Trio<T>(val data1: T, val data2: T, val data3: T) {
|
||||
companion object {
|
||||
public fun <V> from(f: (ThreeSide) -> V): Trio<V> = Trio(f(ThreeSide.LEFT), f(ThreeSide.BASE), f(ThreeSide.RIGHT))
|
||||
}
|
||||
|
||||
public fun <V> map(f: (T) -> V): Trio<V> = Trio(f(data1), f(data2), f(data3))
|
||||
|
||||
public fun <V> map(f: (T, ThreeSide) -> V): Trio<V> = Trio(f(data1, ThreeSide.LEFT), f(data2, ThreeSide.BASE), f(data3, ThreeSide.RIGHT))
|
||||
|
||||
public fun forEach(f: (T, ThreeSide) -> Unit): Unit {
|
||||
f(data1, ThreeSide.LEFT)
|
||||
f(data2, ThreeSide.BASE)
|
||||
f(data3, ThreeSide.RIGHT)
|
||||
}
|
||||
|
||||
public fun invoke(side: ThreeSide): T = side.select(data1, data2, data3)
|
||||
|
||||
override fun toString(): String {
|
||||
return "($data1, $data2, $data3)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.diff.comparison
|
||||
|
||||
import com.intellij.diff.assertTrue
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import com.intellij.util.text.CharSequenceSubSequence
|
||||
import java.util.Random
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
public abstract class AutoTestCase : UsefulTestCase() {
|
||||
protected val RNG: Random = Random()
|
||||
|
||||
private var gotSeedException = false
|
||||
|
||||
protected fun generateText(maxLength: Int, charCount: Int, predefinedChars: Map<Int, Char>): String {
|
||||
val length = RNG.nextInt(maxLength + 1)
|
||||
val builder = StringBuilder(length)
|
||||
|
||||
for (i in 1..length) {
|
||||
val rnd = RNG.nextInt(charCount)
|
||||
val char = predefinedChars.get(rnd) ?: (rnd + 97).toChar();
|
||||
builder.append(char)
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
protected fun getCurrentSeed(): Long {
|
||||
if (gotSeedException) return -1
|
||||
try {
|
||||
val seedField = RNG.javaClass.getDeclaredField("seed")
|
||||
seedField.setAccessible(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.getMessage())
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
public fun textToReadableFormat(text: CharSequence?): String {
|
||||
if (text == null) return "null"
|
||||
return "'" + text.toString().replace('\n', '*').replace('\t', '+') + "'"
|
||||
}
|
||||
|
||||
public fun assertEqualsCharSequences(chunk1: CharSequence, chunk2: CharSequence, ignoreSpaces: Boolean, skipLastNewline: Boolean) {
|
||||
if (ignoreSpaces) {
|
||||
assertTrue(StringUtil.equalsIgnoreWhitespaces(chunk1, chunk2))
|
||||
}
|
||||
else {
|
||||
if (skipLastNewline) {
|
||||
if (StringUtil.equals(chunk1, chunk2)) return
|
||||
if (StringUtil.equals(stripNewline(chunk1), chunk2)) return
|
||||
if (StringUtil.equals(chunk1, stripNewline(chunk2))) return
|
||||
assertTrue(false)
|
||||
}
|
||||
else {
|
||||
assertTrue(StringUtil.equals(chunk1, chunk2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun stripNewline(text: CharSequence): CharSequence? {
|
||||
return when (StringUtil.endsWithChar(text, '\n') ) {
|
||||
true -> CharSequenceSubSequence(text, 0, text.length() - 1)
|
||||
false -> null
|
||||
}
|
||||
}
|
||||
|
||||
protected fun Int.until(a: Int): IntRange = this..a - 1
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.diff.comparison
|
||||
|
||||
public class ComparisonMergeUtilTest : ComparisonMergeUtilTestBase() {
|
||||
public fun testSimpleCases() {
|
||||
chars {
|
||||
("" - "" - "")
|
||||
("" - "" - "").matching()
|
||||
changes()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("xyz" - "xyz" - "xyz")
|
||||
(" " - " " - " ").matching()
|
||||
changes()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("" - "" - "x")
|
||||
("" - "" - "-").matching()
|
||||
changes(mod(0, 0, 0, 0, 0, 1))
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("x" - "yx" - "x")
|
||||
(" " - "- " - " ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("x" - "xy" - "x")
|
||||
(" " - " -" - " ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("xyz" - "xYz" - "xyz")
|
||||
(" - " - " - " - " - ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("xyz" - "XyZ" - "xyz")
|
||||
("- -" - "- -" - "- -").matching()
|
||||
test()
|
||||
}
|
||||
}
|
||||
|
||||
public fun testConflictTYpes() {
|
||||
chars {
|
||||
("abcd" - "abcd" - "abXcd")
|
||||
(" " - " " - " - ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abXcd" - "abcd" - "abXcd")
|
||||
(" - " - " " - " - ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abcd" - "abXcd" - "abXcd")
|
||||
(" " - " - " - " - ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abcd" - "abXcd" - "abcd")
|
||||
(" " - " - " - " ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abcd" - "abXcd" - "abYcd")
|
||||
(" " - " - " - " - ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abXcd" - "abXcd" - "abYcd")
|
||||
(" - " - " - " - " - ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abYcd" - "abXcd" - "abYcd")
|
||||
(" - " - " - " - " - ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abYcd" - "abXcd" - "abZcd")
|
||||
(" - " - " - " - " - ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abXcd" - "abcd" - "abYcd")
|
||||
(" - " - " " - " - ").matching()
|
||||
test()
|
||||
}
|
||||
}
|
||||
|
||||
public fun testBoundaryConflicts() {
|
||||
chars {
|
||||
("abcd" - "abcd" - "abcdx")
|
||||
(" " - " " - " -").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abcd" - "abcdx" - "abcdx")
|
||||
(" " - " -" - " -").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abcd" - "abcdx" - "abcdy")
|
||||
(" " - " -" - " -").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abcdz" - "abcdx" - "abcdy")
|
||||
(" -" - " -" - " -").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("xabcd" - "abcd" - "abcd")
|
||||
("- " - " " - " ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("xabcd" - "yabcd" - "abcd")
|
||||
("- " - "- " - " ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("abcd" - "yabcd" - "abcd")
|
||||
(" " - "- " - " ").matching()
|
||||
test()
|
||||
}
|
||||
}
|
||||
|
||||
public fun testMultipleChanges() {
|
||||
chars {
|
||||
("XXbXcXXeX" - "XyXzXXnXkX" - "XqXXeXrXX")
|
||||
(" - - - " - " - - - - " - " - - - ").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("Ax" - "z" - "zA")
|
||||
("--" - "-" - "--").matching()
|
||||
test()
|
||||
}
|
||||
|
||||
chars {
|
||||
("ayz" - "xyz" - "xyq")
|
||||
("- -" - "- -" - "- -").matching()
|
||||
test()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.diff.comparison
|
||||
|
||||
import com.intellij.diff.DiffTestCase
|
||||
import com.intellij.diff.DiffTestCase.Trio
|
||||
import com.intellij.diff.assertEquals
|
||||
import com.intellij.diff.assertOrderedEquals
|
||||
import com.intellij.diff.assertTrue
|
||||
import com.intellij.diff.fragments.MergeLineFragment
|
||||
import com.intellij.diff.util.IntPair
|
||||
import com.intellij.diff.util.ThreeSide
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.impl.DocumentImpl
|
||||
import com.intellij.openapi.util.Couple
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import java.util.BitSet
|
||||
|
||||
public abstract class ComparisonMergeUtilTestBase : DiffTestCase() {
|
||||
private fun doCharTest(texts: Trio<Document>, expected: List<Change>?, matchings: Trio<BitSet>?) {
|
||||
val iterable1 = ByChar.compare(texts.data2.getCharsSequence(), texts.data1.getCharsSequence(), INDICATOR)
|
||||
val iterable2 = ByChar.compare(texts.data2.getCharsSequence(), texts.data3.getCharsSequence(), INDICATOR)
|
||||
|
||||
val fragments = ComparisonMergeUtil.buildFair(iterable1, iterable2, INDICATOR)
|
||||
val actual = convertDiffFragments(fragments)
|
||||
|
||||
checkConsistency(actual, texts)
|
||||
if (matchings != null) checkDiffMatching(actual, matchings)
|
||||
if (expected != null) checkDiffChanges(actual, expected)
|
||||
}
|
||||
|
||||
private fun checkConsistency(actual: List<Change>, texts: Trio<Document>) {
|
||||
var lasts = Trio(-1, -1, -1)
|
||||
|
||||
for (change in actual) {
|
||||
val starts = change.starts
|
||||
val ends = change.ends
|
||||
|
||||
var empty = true
|
||||
var squashed = true
|
||||
ThreeSide.values().forEach {
|
||||
val start = starts(it)
|
||||
val end = ends(it)
|
||||
val last = lasts(it)
|
||||
|
||||
assertTrue(last <= start)
|
||||
assertTrue(start <= end)
|
||||
empty = empty && (start == end)
|
||||
squashed = squashed && (start == last)
|
||||
}
|
||||
|
||||
assertTrue(!empty)
|
||||
assertTrue(!squashed)
|
||||
|
||||
lasts = ends
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDiffChanges(actual: List<Change>, expected: List<Change>) {
|
||||
assertOrderedEquals(actual, expected)
|
||||
}
|
||||
|
||||
private fun checkDiffMatching(changes: List<Change>, matchings: Trio<BitSet>) {
|
||||
val sets = Trio(BitSet(), BitSet(), BitSet())
|
||||
|
||||
for (change in changes) {
|
||||
sets.forEach({ set: BitSet, side: ThreeSide -> set.set(change.start(side), change.end(side)) })
|
||||
}
|
||||
|
||||
assertEquals(matchings.data1, sets.data1)
|
||||
assertEquals(matchings.data2, sets.data2)
|
||||
assertEquals(matchings.data3, sets.data3)
|
||||
}
|
||||
|
||||
private fun convertDiffFragments(fragments: List<MergeLineFragment>): List<Change> {
|
||||
return fragments.map {
|
||||
Change(
|
||||
it.getStartLine(ThreeSide.LEFT), it.getEndLine(ThreeSide.LEFT),
|
||||
it.getStartLine(ThreeSide.BASE), it.getEndLine(ThreeSide.BASE),
|
||||
it.getStartLine(ThreeSide.RIGHT), it.getEndLine(ThreeSide.RIGHT))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum class TestType {
|
||||
CHAR
|
||||
}
|
||||
|
||||
public inner class MergeTestBuilder(val type: TestType) {
|
||||
private var isExecuted: Boolean = false
|
||||
|
||||
private var texts: Trio<Document>? = null
|
||||
|
||||
private var changes: List<Change>? = null
|
||||
private var matching: Trio<BitSet>? = null
|
||||
|
||||
public fun assertExecuted() {
|
||||
assertTrue(isExecuted)
|
||||
}
|
||||
|
||||
public fun test() {
|
||||
isExecuted = true
|
||||
|
||||
assertTrue(changes != null || matching != null)
|
||||
|
||||
when (type) {
|
||||
TestType.CHAR -> doCharTest(texts!!, changes, matching)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public fun String.minus(v: String): Couple<String> {
|
||||
return Couple(this, v)
|
||||
}
|
||||
|
||||
public fun Couple<String>.minus(v: String): Helper {
|
||||
return Helper(Trio(this.first, this.second, v))
|
||||
}
|
||||
|
||||
public inner class Helper(val texts: Trio<String>) {
|
||||
init {
|
||||
val builder = this@MergeTestBuilder
|
||||
if (builder.texts == null) {
|
||||
builder.texts = texts.map { it -> DocumentImpl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
public fun matching() {
|
||||
matching = texts.map { it -> parseMatching(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public fun changes(vararg expected: Change): Unit {
|
||||
changes = ContainerUtil.list(*expected)
|
||||
}
|
||||
|
||||
|
||||
public fun mod(line1: Int, line2: Int, line3: Int, count1: Int, count2: Int, count3: Int): Change {
|
||||
return Change(line1, line1 + count1, line2, line2 + count2, line3, line3 + count3)
|
||||
}
|
||||
}
|
||||
|
||||
public fun chars(f: MergeTestBuilder.() -> Unit) {
|
||||
doTest(TestType.CHAR, f)
|
||||
}
|
||||
|
||||
private fun doTest(type: TestType, f: MergeTestBuilder.() -> Unit) {
|
||||
val builder = MergeTestBuilder(type)
|
||||
builder.f()
|
||||
builder.assertExecuted()
|
||||
}
|
||||
|
||||
|
||||
public data class Change(start1: Int, end1: Int, start2: Int, end2: Int, start3: Int, end3: Int)
|
||||
: Trio<IntPair>(IntPair(start1, end1), IntPair(start2, end2), IntPair(start3, end3)) {
|
||||
|
||||
val start1 = start(ThreeSide.LEFT)
|
||||
val start2 = start(ThreeSide.BASE)
|
||||
val start3 = start(ThreeSide.RIGHT)
|
||||
|
||||
val end1 = end(ThreeSide.LEFT)
|
||||
val end2 = end(ThreeSide.BASE)
|
||||
val end3 = end(ThreeSide.RIGHT)
|
||||
|
||||
val starts = Trio(start1, start2, start3)
|
||||
val ends = Trio(end1, end2, end3)
|
||||
|
||||
public fun start(side: ThreeSide): Int = this(side).val1
|
||||
public fun end(side: ThreeSide): Int = this(side).val2
|
||||
|
||||
override fun toString(): String {
|
||||
return "($start1, $end1) - ($start2, $end2) - ($start3, $end3)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.intellij.diff.comparison
|
||||
|
||||
import com.intellij.diff.DiffTestCase
|
||||
import com.intellij.diff.assertEquals
|
||||
import com.intellij.diff.assertFalse
|
||||
import com.intellij.diff.assertTrue
|
||||
@@ -23,27 +24,11 @@ import com.intellij.diff.fragments.LineFragment
|
||||
import com.intellij.diff.util.DiffUtil
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.impl.DocumentImpl
|
||||
import com.intellij.openapi.progress.DumbProgressIndicator
|
||||
import com.intellij.openapi.util.Couple
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.util.containers.HashMap
|
||||
|
||||
public class ComparisonUtilAutoTest : AutoTestCase() {
|
||||
private var oldRegistryValue: Boolean = false
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
oldRegistryValue = REGISTRY.asBoolean()
|
||||
REGISTRY.setValue(true)
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
REGISTRY.setValue(oldRegistryValue)
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
public class ComparisonUtilAutoTest : DiffTestCase() {
|
||||
public fun testChar() {
|
||||
doTestChar(System.currentTimeMillis(), 30, 30)
|
||||
}
|
||||
@@ -72,7 +57,7 @@ public class ComparisonUtilAutoTest : AutoTestCase() {
|
||||
val sequence2 = text2.getCharsSequence()
|
||||
|
||||
val fragments = MANAGER.compareLinesInner(sequence1, sequence2, policy, INDICATOR)
|
||||
debugData.set(fragments)
|
||||
debugData.put("Fragments", fragments)
|
||||
|
||||
checkResultLine(text1, text2, fragments, policy, true)
|
||||
}
|
||||
@@ -86,10 +71,10 @@ public class ComparisonUtilAutoTest : AutoTestCase() {
|
||||
val sequence2 = text2.getCharsSequence()
|
||||
|
||||
val fragments = MANAGER.compareLinesInner(sequence1, sequence2, policy, INDICATOR)
|
||||
debugData.set(fragments)
|
||||
debugData.put("Fragments", fragments)
|
||||
|
||||
val squashedFragments = MANAGER.squash(fragments)
|
||||
debugData.set(listOf(fragments, squashedFragments))
|
||||
debugData.put("Squashed Fragments", squashedFragments)
|
||||
|
||||
checkResultLine(text1, text2, squashedFragments, policy, false)
|
||||
}
|
||||
@@ -103,10 +88,10 @@ public class ComparisonUtilAutoTest : AutoTestCase() {
|
||||
val sequence2 = text2.getCharsSequence()
|
||||
|
||||
val fragments = MANAGER.compareLinesInner(sequence1, sequence2, policy, INDICATOR)
|
||||
debugData.set(fragments)
|
||||
debugData.put("Fragments", fragments)
|
||||
|
||||
val processed = MANAGER.processBlocks(fragments, sequence1, sequence2, policy, true, true)
|
||||
debugData.set(listOf(fragments, processed))
|
||||
debugData.put("Processed Fragments", processed)
|
||||
|
||||
checkResultLine(text1, text2, processed, policy, false)
|
||||
}
|
||||
@@ -120,7 +105,7 @@ public class ComparisonUtilAutoTest : AutoTestCase() {
|
||||
val sequence2 = text2.getCharsSequence()
|
||||
|
||||
val fragments = MANAGER.compareChars(sequence1, sequence2, policy, INDICATOR)
|
||||
debugData.set(fragments)
|
||||
debugData.put("Fragments", fragments)
|
||||
|
||||
checkResultChar(sequence1, sequence2, fragments, policy)
|
||||
}
|
||||
@@ -134,54 +119,30 @@ public class ComparisonUtilAutoTest : AutoTestCase() {
|
||||
val sequence2 = text2.getCharsSequence()
|
||||
|
||||
val fragments = MANAGER.compareWords(sequence1, sequence2, policy, INDICATOR)
|
||||
debugData.set(fragments)
|
||||
debugData.put("Fragments", fragments)
|
||||
|
||||
checkResultWord(sequence1, sequence2, fragments, policy)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doTest(seed: Long, runs: Int, maxLength: Int, policies: List<ComparisonPolicy>,
|
||||
test: (Document, Document, ComparisonPolicy, Ref<Any>) -> Unit) {
|
||||
RNG.setSeed(seed)
|
||||
test: (Document, Document, ComparisonPolicy, DiffTestCase.DebugData) -> Unit) {
|
||||
doAutoTest(seed, runs) { debugData ->
|
||||
debugData.put("MaxLength", maxLength)
|
||||
|
||||
var policy: ComparisonPolicy? = null
|
||||
var lastSeed: Long = -1;
|
||||
val debugData = Ref<Any>()
|
||||
val text1 = DocumentImpl(generateText(maxLength))
|
||||
val text2 = DocumentImpl(generateText(maxLength))
|
||||
|
||||
for (i in 1..runs) {
|
||||
if (i % 1000 == 0) println(i)
|
||||
var text1: Document? = null
|
||||
var text2: Document? = null
|
||||
try {
|
||||
lastSeed = getCurrentSeed()
|
||||
debugData.put("Text1", textToReadableFormat(text1.getCharsSequence()))
|
||||
debugData.put("Text2", textToReadableFormat(text2.getCharsSequence()))
|
||||
|
||||
text1 = generateText(maxLength)
|
||||
text2 = generateText(maxLength)
|
||||
|
||||
for (comparisonPolicy in policies) {
|
||||
policy = comparisonPolicy
|
||||
test(text1, text2, comparisonPolicy, debugData)
|
||||
}
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
println("Seed: " + seed)
|
||||
println("Runs: " + runs)
|
||||
println("MaxLength: " + maxLength)
|
||||
println("Policy: " + policy!!)
|
||||
println("I: " + i)
|
||||
println("Current seed: " + lastSeed)
|
||||
println("Text1: " + textToReadableFormat(text1?.getCharsSequence()))
|
||||
println("Text2: " + textToReadableFormat(text2?.getCharsSequence()))
|
||||
println("Debug Data: " + debugData.get())
|
||||
throw e
|
||||
for (comparisonPolicy in policies) {
|
||||
debugData.put("Policy", comparisonPolicy)
|
||||
test(text1, text2, comparisonPolicy, debugData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateText(maxLength: Int): Document {
|
||||
return DocumentImpl(generateText(maxLength, CHAR_COUNT, CHAR_TABLE))
|
||||
}
|
||||
|
||||
private fun checkResultLine(text1: Document, text2: Document, fragments: List<LineFragment>, policy: ComparisonPolicy, allowNonSquashed: Boolean) {
|
||||
checkLineConsistency(text1, text2, fragments, allowNonSquashed)
|
||||
|
||||
@@ -357,22 +318,4 @@ public class ComparisonUtilAutoTest : AutoTestCase() {
|
||||
private fun Document.subsequence(start: Int, end: Int): CharSequence {
|
||||
return this.getCharsSequence().subSequence(start, end)
|
||||
}
|
||||
|
||||
private fun getLineCount(document: Document): Int {
|
||||
return Math.max(1, document.getLineCount())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val REGISTRY = Registry.get("diff.verify.iterable");
|
||||
|
||||
private val INDICATOR = DumbProgressIndicator.INSTANCE
|
||||
private val MANAGER = ComparisonManagerImpl()
|
||||
|
||||
private val CHAR_COUNT = 12
|
||||
private val CHAR_TABLE: Map<Int, Char> = {
|
||||
val map = HashMap<Int, Char>()
|
||||
listOf('\n', '\n', '\t', ' ', ' ', '.', '<', '!').forEachIndexed { i, c -> map.put(i, c) }
|
||||
map
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package com.intellij.diff.comparison
|
||||
|
||||
import com.intellij.diff.assertEquals
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import junit.framework.TestCase
|
||||
|
||||
public class ComparisonUtilTest : UsefulTestCase() {
|
||||
public fun testTrimEquals() {
|
||||
@@ -61,12 +61,12 @@ public class ComparisonUtilTest : UsefulTestCase() {
|
||||
}
|
||||
|
||||
private fun doTestTrim(expected: Boolean, string1: String, string2: String) {
|
||||
doTest(expected, string1, string2, ComparisonPolicy.TRIM_WHITESPACES);
|
||||
doTest(expected, string1, string2, ComparisonPolicy.TRIM_WHITESPACES)
|
||||
}
|
||||
|
||||
private fun doTest(expected: Boolean, string1: String, string2: String, policy: ComparisonPolicy) {
|
||||
val result = MANAGER.isEquals(string1, string2, policy);
|
||||
TestCase.assertEquals("---\n" + string1 + "\n---\n" + string2 + "\n---", expected, result);
|
||||
val result = MANAGER.isEquals(string1, string2, policy)
|
||||
assertEquals(expected, result, "---\n" + string1 + "\n---\n" + string2 + "\n---")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -15,39 +15,16 @@
|
||||
*/
|
||||
package com.intellij.diff.comparison
|
||||
|
||||
import com.intellij.diff.assertEquals
|
||||
import com.intellij.diff.assertNull
|
||||
import com.intellij.diff.assertOrderedEquals
|
||||
import com.intellij.diff.assertTrue
|
||||
import com.intellij.diff.*
|
||||
import com.intellij.diff.fragments.DiffFragment
|
||||
import com.intellij.diff.fragments.LineFragment
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.impl.DocumentImpl
|
||||
import com.intellij.openapi.progress.DumbProgressIndicator
|
||||
import com.intellij.openapi.util.Couple
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import java.util.BitSet
|
||||
|
||||
public abstract class ComparisonUtilTestBase : UsefulTestCase() {
|
||||
private var oldRegistryValue: Boolean = false
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
oldRegistryValue = REGISTRY.asBoolean()
|
||||
REGISTRY.setValue(true)
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
REGISTRY.setValue(oldRegistryValue)
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
//
|
||||
// Impl
|
||||
//
|
||||
|
||||
public abstract class ComparisonUtilTestBase : DiffTestCase() {
|
||||
private fun doLineTest(before: Document, after: Document, expected: List<Change>?, policy: ComparisonPolicy) {
|
||||
val fragments = MANAGER.compareLines(before.getCharsSequence(), after.getCharsSequence(), policy, INDICATOR)
|
||||
checkConsistency(fragments, before, after)
|
||||
@@ -174,10 +151,6 @@ public abstract class ComparisonUtilTestBase : UsefulTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLineCount(document: Document): Int {
|
||||
return Math.max(1, document.getLineCount())
|
||||
}
|
||||
|
||||
//
|
||||
// Test Builder
|
||||
//
|
||||
@@ -187,7 +160,7 @@ public abstract class ComparisonUtilTestBase : UsefulTestCase() {
|
||||
}
|
||||
|
||||
public inner class TestBuilder(private val type: TestType) {
|
||||
private var isExecuted: Boolean = false;
|
||||
private var isExecuted: Boolean = false
|
||||
|
||||
private var before: Document? = null
|
||||
private var after: Document? = null
|
||||
@@ -200,18 +173,18 @@ public abstract class ComparisonUtilTestBase : UsefulTestCase() {
|
||||
private var trimMatching: Couple<BitSet>? = null
|
||||
private var ignoreMatching: Couple<BitSet>? = null
|
||||
|
||||
private var shouldSquash: Boolean = false;
|
||||
private var shouldTrim: Boolean = false;
|
||||
private var shouldSquash: Boolean = false
|
||||
private var shouldTrim: Boolean = false
|
||||
|
||||
private fun changes(policy: ComparisonPolicy): List<Change>? = when (policy) {
|
||||
ComparisonPolicy.IGNORE_WHITESPACES -> ignoreChanges ?: trimChanges ?: defaultChanges;
|
||||
ComparisonPolicy.TRIM_WHITESPACES -> trimChanges ?: defaultChanges;
|
||||
ComparisonPolicy.IGNORE_WHITESPACES -> ignoreChanges ?: trimChanges ?: defaultChanges
|
||||
ComparisonPolicy.TRIM_WHITESPACES -> trimChanges ?: defaultChanges
|
||||
ComparisonPolicy.DEFAULT -> defaultChanges
|
||||
}
|
||||
|
||||
private fun matchings(policy: ComparisonPolicy): Couple<BitSet>? = when (policy) {
|
||||
ComparisonPolicy.IGNORE_WHITESPACES -> ignoreMatching ?: trimMatching ?: defaultMatching;
|
||||
ComparisonPolicy.TRIM_WHITESPACES -> trimMatching ?: defaultMatching;
|
||||
ComparisonPolicy.IGNORE_WHITESPACES -> ignoreMatching ?: trimMatching ?: defaultMatching
|
||||
ComparisonPolicy.TRIM_WHITESPACES -> trimMatching ?: defaultMatching
|
||||
ComparisonPolicy.DEFAULT -> defaultMatching
|
||||
}
|
||||
|
||||
@@ -221,7 +194,7 @@ public abstract class ComparisonUtilTestBase : UsefulTestCase() {
|
||||
|
||||
private fun run(policy: ComparisonPolicy) {
|
||||
try {
|
||||
isExecuted = true;
|
||||
isExecuted = true
|
||||
|
||||
val change = changes(policy)
|
||||
val matchings = matchings(policy)
|
||||
@@ -239,22 +212,10 @@ public abstract class ComparisonUtilTestBase : UsefulTestCase() {
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
println("Policy: " + policy.name())
|
||||
throw e;
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseSource(string: String): String = string.replace('_', '\n')
|
||||
|
||||
private fun parseMatching(before: String, after: String): Couple<BitSet> {
|
||||
return Couple.of(parseMatching(before), parseMatching(after))
|
||||
}
|
||||
|
||||
private fun parseMatching(matching: String): BitSet {
|
||||
val set = BitSet()
|
||||
matching.filterNot { it == '.' }.forEachIndexed { i, c -> if (c != ' ') set.set(i) }
|
||||
return set
|
||||
}
|
||||
|
||||
|
||||
public fun testAll() {
|
||||
testDefault()
|
||||
@@ -339,8 +300,8 @@ public abstract class ComparisonUtilTestBase : UsefulTestCase() {
|
||||
|
||||
|
||||
public fun postprocess(squash: Boolean, trim: Boolean): Unit {
|
||||
shouldSquash = squash;
|
||||
shouldTrim = trim;
|
||||
shouldSquash = squash
|
||||
shouldTrim = trim
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,11 +333,4 @@ public abstract class ComparisonUtilTestBase : UsefulTestCase() {
|
||||
return "(" + start1 + ", " + end1 + ") - (" + start2 + ", " + end2 + ")"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val REGISTRY = Registry.get("diff.verify.iterable");
|
||||
|
||||
private val INDICATOR = DumbProgressIndicator.INSTANCE
|
||||
private val MANAGER = ComparisonManagerImpl()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.diff.merge
|
||||
|
||||
import com.intellij.diff.DiffTestCase
|
||||
import com.intellij.diff.assertEquals
|
||||
import com.intellij.diff.assertTrue
|
||||
import com.intellij.diff.merge.MergeTestBase.TestBuilder
|
||||
import com.intellij.diff.util.Side
|
||||
import com.intellij.diff.util.ThreeSide
|
||||
import kotlin.test.fail
|
||||
|
||||
public class MergeAutoTest : MergeTestBase() {
|
||||
companion object {
|
||||
private val MODIFICATION_CYCLE_COUNT = 5
|
||||
private val MODIFICATION_CYCLE_SIZE = 3
|
||||
}
|
||||
|
||||
public fun testUndo() {
|
||||
doUndoTest(System.currentTimeMillis(), 10, 300)
|
||||
}
|
||||
|
||||
private fun doUndoTest(seed: Long, runs: Int, maxLength: Int) {
|
||||
doTest(seed, runs, maxLength) { text1, text2, text3, debugData ->
|
||||
testN(text1, text2, text3) {
|
||||
if (changes.size() == 0) {
|
||||
assertEquals(text1, text2)
|
||||
assertEquals(text1, text3)
|
||||
assertEquals(text2, text3)
|
||||
return@testN
|
||||
}
|
||||
|
||||
for (m in 1..MODIFICATION_CYCLE_COUNT) {
|
||||
checkUndo(MODIFICATION_CYCLE_SIZE) {
|
||||
for (n in 1..MODIFICATION_CYCLE_SIZE) {
|
||||
val operation = RNG.nextInt(3)
|
||||
when (operation) {
|
||||
0 -> doApply()
|
||||
1 -> doIgnore()
|
||||
2 -> doReplace()
|
||||
else -> fail()
|
||||
}
|
||||
checkChangesRangeOrdering(changes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestBuilder.doApply(): Unit {
|
||||
val index = RNG.nextInt(changes.size())
|
||||
val change = changes.get(index)
|
||||
|
||||
val side = Side.fromLeft(RNG.nextBoolean())
|
||||
val modifier = RNG.nextBoolean()
|
||||
|
||||
command(change) { viewer.replaceChange(change, side, modifier) }
|
||||
}
|
||||
|
||||
private fun TestBuilder.doIgnore(): Unit {
|
||||
val index = RNG.nextInt(changes.size())
|
||||
val change = changes.get(index)
|
||||
|
||||
val side = Side.fromLeft(RNG.nextBoolean())
|
||||
val modifier = RNG.nextBoolean()
|
||||
|
||||
command(change) { viewer.ignoreChange(change, side, modifier) }
|
||||
}
|
||||
|
||||
private fun TestBuilder.doReplace(): Unit {
|
||||
val length = document.getTextLength()
|
||||
|
||||
var index1: Int = 0;
|
||||
var index2: Int = 0;
|
||||
if (length != 0) {
|
||||
index1 = RNG.nextInt(length)
|
||||
index2 = index1 + RNG.nextInt(length - index1)
|
||||
}
|
||||
|
||||
val newText = generateText(30)
|
||||
|
||||
write { document.replaceString(index1, index2, newText) }
|
||||
}
|
||||
|
||||
private fun doTest(seed: Long, runs: Int, maxLength: Int, test: (String, String, String, DiffTestCase.DebugData) -> Unit) {
|
||||
doAutoTest(seed, runs) { debugData ->
|
||||
debugData.put("MaxLength", maxLength)
|
||||
|
||||
val text1 = generateText(maxLength)
|
||||
val text2 = generateText(maxLength)
|
||||
val text3 = generateText(maxLength)
|
||||
|
||||
debugData.put("Text1", textToReadableFormat(text1))
|
||||
debugData.put("Text2", textToReadableFormat(text2))
|
||||
debugData.put("Text3", textToReadableFormat(text3))
|
||||
|
||||
test(text1, text2, text3, debugData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkChangesRangeOrdering(changes: List<TextMergeChange>) {
|
||||
for (i in 1..changes.size() - 1) {
|
||||
val lastEnd = changes.get(i - 1).getEndLine(ThreeSide.BASE)
|
||||
val start = changes.get(i).getStartLine(ThreeSide.BASE)
|
||||
assertTrue(lastEnd <= start, "lastEnd: $lastEnd, start: $start")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,614 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.diff.merge
|
||||
|
||||
import com.intellij.diff.merge.MergeTestBase.SidesState.BOTH
|
||||
import com.intellij.diff.merge.MergeTestBase.SidesState.LEFT
|
||||
import com.intellij.diff.merge.MergeTestBase.SidesState.NONE
|
||||
import com.intellij.diff.merge.MergeTestBase.SidesState.RIGHT
|
||||
import com.intellij.diff.util.Side
|
||||
import com.intellij.diff.util.TextDiffType.CONFLICT
|
||||
import com.intellij.diff.util.TextDiffType.DELETED
|
||||
import com.intellij.diff.util.TextDiffType.INSERTED
|
||||
import com.intellij.diff.util.TextDiffType.MODIFIED
|
||||
import com.intellij.openapi.diff.DiffBundle
|
||||
|
||||
public class MergeTest : MergeTestBase() {
|
||||
public fun testChangeTypes() {
|
||||
test ("", "", "", 0) {
|
||||
}
|
||||
|
||||
test ("x", "x", "x", 0) {
|
||||
}
|
||||
|
||||
test ("x_y_z a x", "x_y_z a x", "x_y_z a x", 0) {
|
||||
}
|
||||
|
||||
test1 ("x", "x", "y") {
|
||||
0.assertType(MODIFIED, RIGHT)
|
||||
0.assertContent("x", 0, 1)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("x", "y", "x") {
|
||||
0.assertType(MODIFIED, BOTH)
|
||||
0.assertContent("y", 0, 1)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test2 ("x_Y", "Y", "Y_z") {
|
||||
0.assertType(INSERTED, LEFT)
|
||||
0.assertContent("", 0, 0)
|
||||
0.assertResolved(NONE)
|
||||
|
||||
1.assertType(INSERTED, RIGHT)
|
||||
1.assertContent("", 1, 1)
|
||||
1.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test2 ("Y_z", "x_Y_z", "x_Y") {
|
||||
0.assertType(DELETED, LEFT)
|
||||
0.assertContent("x", 0, 1)
|
||||
0.assertResolved(NONE)
|
||||
|
||||
1.assertType(DELETED, RIGHT)
|
||||
1.assertContent("z", 2, 3)
|
||||
1.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("X_Z", "X_y_Z", "X_Z") {
|
||||
0.assertType(DELETED, BOTH)
|
||||
0.assertContent("y", 1, 2)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("X_y_Z", "X_Z", "X_y_Z") {
|
||||
0.assertType(INSERTED, BOTH)
|
||||
0.assertContent("", 1, 1)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1("x", "y", "z") {
|
||||
0.assertType(CONFLICT, BOTH)
|
||||
0.assertContent("y", 0, 1)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("z_Y", "x_Y", "Y") {
|
||||
0.assertType(CONFLICT, BOTH)
|
||||
0.assertContent("x", 0, 1)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("z_Y", "x_Y", "k_x_Y") {
|
||||
0.assertType(CONFLICT, BOTH)
|
||||
0.assertContent("x", 0, 1)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("x_Y", "Y", "z_Y") {
|
||||
0.assertType(CONFLICT, BOTH)
|
||||
0.assertContent("", 0, 0)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("x_Y", "Y", "z_x_Y") {
|
||||
0.assertType(CONFLICT, BOTH)
|
||||
0.assertContent("", 0, 0)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("x_Y", "x_z_Y", "z_Y") {
|
||||
0.assertType(CONFLICT, BOTH)
|
||||
0.assertContent("x_z", 0, 2)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
}
|
||||
|
||||
public fun testLastLine() {
|
||||
test1 ("x", "x_", "x") {
|
||||
0.assertType(DELETED, BOTH)
|
||||
0.assertContent("", 1, 2)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("x_", "x_", "x") {
|
||||
0.assertType(DELETED, RIGHT)
|
||||
0.assertContent("", 1, 2)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("x_", "x_", "x_y") {
|
||||
0.assertType(MODIFIED,RIGHT)
|
||||
0.assertContent("", 1, 2)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("x", "x_", "x_y") {
|
||||
0.assertType(CONFLICT,BOTH)
|
||||
0.assertContent("", 1, 2)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
|
||||
test1 ("x_", "x", "x_y") {
|
||||
0.assertType(CONFLICT,BOTH)
|
||||
0.assertContent("", 1, 1)
|
||||
0.assertResolved(NONE)
|
||||
}
|
||||
}
|
||||
|
||||
public fun testModifications() {
|
||||
test1 ("x", "x", "y") {
|
||||
0.apply(Side.RIGHT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("y")
|
||||
|
||||
assertContent("y")
|
||||
}
|
||||
|
||||
test1 ("x", "x", "y") {
|
||||
0.apply(Side.LEFT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("x")
|
||||
|
||||
assertContent("x")
|
||||
}
|
||||
|
||||
test1 ("X_x_Z", "X_x_Z", "X_y_Z") {
|
||||
0.apply(Side.RIGHT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("y")
|
||||
|
||||
assertContent("X_y_Z")
|
||||
}
|
||||
|
||||
test1 ("z", "x", "y") {
|
||||
0.apply(Side.RIGHT)
|
||||
0.assertResolved(RIGHT)
|
||||
0.assertContent("y")
|
||||
|
||||
0.apply(Side.LEFT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("y_z")
|
||||
|
||||
assertContent("y_z")
|
||||
}
|
||||
|
||||
test1 ("z", "x", "y") {
|
||||
0.apply(Side.LEFT)
|
||||
0.assertResolved(LEFT)
|
||||
0.assertContent("z")
|
||||
|
||||
0.apply(Side.RIGHT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("z_y")
|
||||
|
||||
assertContent("z_y")
|
||||
}
|
||||
|
||||
test1 ("X", "X", "X_y") {
|
||||
0.apply(Side.RIGHT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("y")
|
||||
|
||||
assertContent("X_y")
|
||||
}
|
||||
|
||||
test1 ("X", "X", "X_y") {
|
||||
0.apply(Side.LEFT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("")
|
||||
|
||||
assertContent("X")
|
||||
}
|
||||
|
||||
test1 ("X_z", "X", "X_y") {
|
||||
0.apply(Side.RIGHT)
|
||||
0.assertResolved(RIGHT)
|
||||
0.assertContent("y")
|
||||
|
||||
0.apply(Side.LEFT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("y_z")
|
||||
|
||||
assertContent("X_y_z")
|
||||
}
|
||||
|
||||
test1 ("X_z", "X_y", "X") {
|
||||
0.apply(Side.LEFT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("z")
|
||||
|
||||
assertContent("X_z")
|
||||
}
|
||||
|
||||
test1 ("X_z", "X_y", "X") {
|
||||
0.apply(Side.RIGHT)
|
||||
0.assertResolved(RIGHT)
|
||||
0.assertContent("")
|
||||
|
||||
0.apply(Side.LEFT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("z")
|
||||
|
||||
assertContent("X_z")
|
||||
}
|
||||
}
|
||||
|
||||
public fun testModificationsIgnore() {
|
||||
test1 ("x", "x", "y") {
|
||||
0.ignore(Side.RIGHT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("x")
|
||||
|
||||
assertContent("x")
|
||||
}
|
||||
|
||||
test1 ("x", "x", "y") {
|
||||
0.ignore(Side.LEFT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("x")
|
||||
|
||||
assertContent("x")
|
||||
}
|
||||
|
||||
test1 ("z", "x", "y") {
|
||||
0.ignore(Side.RIGHT)
|
||||
0.assertResolved(RIGHT)
|
||||
0.assertContent("x")
|
||||
|
||||
0.ignore(Side.LEFT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("x")
|
||||
|
||||
assertContent("x")
|
||||
}
|
||||
|
||||
test1 ("z", "x", "y") {
|
||||
0.apply(Side.RIGHT)
|
||||
0.assertResolved(RIGHT)
|
||||
0.assertContent("y")
|
||||
|
||||
0.ignore(Side.LEFT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("y")
|
||||
|
||||
assertContent("y")
|
||||
}
|
||||
|
||||
test1 ("z", "x", "y") {
|
||||
0.ignore(Side.RIGHT)
|
||||
0.assertResolved(RIGHT)
|
||||
0.assertContent("x")
|
||||
|
||||
0.apply(Side.LEFT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("z")
|
||||
|
||||
assertContent("z")
|
||||
}
|
||||
|
||||
test1 ("X_z", "X_y", "X") {
|
||||
0.ignore(Side.RIGHT)
|
||||
0.assertResolved(RIGHT)
|
||||
0.assertContent("y")
|
||||
|
||||
0.apply(Side.LEFT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("z")
|
||||
|
||||
assertContent("X_z")
|
||||
}
|
||||
|
||||
test1 ("X_z", "X_y", "X") {
|
||||
0.ignore(Side.LEFT)
|
||||
0.assertResolved(LEFT)
|
||||
0.assertContent("y")
|
||||
|
||||
0.apply(Side.RIGHT)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("")
|
||||
|
||||
assertContent("X")
|
||||
}
|
||||
}
|
||||
|
||||
public fun testModificationsModifiers() {
|
||||
test1 ("x", "x", "y") {
|
||||
0.apply(Side.RIGHT, true)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("y")
|
||||
|
||||
assertContent("y")
|
||||
}
|
||||
|
||||
test1 ("x", "x", "y") {
|
||||
0.ignore(Side.RIGHT, true)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("x")
|
||||
|
||||
assertContent("x")
|
||||
}
|
||||
|
||||
test1 ("z", "x", "y") {
|
||||
0.apply(Side.RIGHT, true)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("y")
|
||||
|
||||
assertContent("y")
|
||||
}
|
||||
|
||||
test1 ("z", "x", "y") {
|
||||
0.ignore(Side.RIGHT, true)
|
||||
0.assertResolved(BOTH)
|
||||
0.assertContent("x")
|
||||
|
||||
assertContent("x")
|
||||
}
|
||||
}
|
||||
|
||||
public fun testUndoSimple() {
|
||||
test1 ("x", "y", "z") {
|
||||
checkUndo(1) {
|
||||
0.apply(Side.RIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
test1 ("x", "y", "z") {
|
||||
checkUndo(1) {
|
||||
0.apply(Side.RIGHT, true)
|
||||
}
|
||||
}
|
||||
|
||||
test1 ("x", "y", "z") {
|
||||
checkUndo(1) {
|
||||
0.ignore(Side.RIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
test1 ("x", "y", "z") {
|
||||
checkUndo(1) {
|
||||
0.ignore(Side.RIGHT, true)
|
||||
}
|
||||
}
|
||||
|
||||
test1 ("x", "y", "z") {
|
||||
checkUndo(2) {
|
||||
0.apply(Side.RIGHT)
|
||||
0.apply(Side.LEFT)
|
||||
}
|
||||
}
|
||||
|
||||
test1 ("x", "y", "z") {
|
||||
checkUndo(2) {
|
||||
0.apply(Side.RIGHT)
|
||||
0.apply(Side.RIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
test1 ("X", "X_y", "X") {
|
||||
checkUndo(1) {
|
||||
0.apply(Side.RIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
test1 ("X", "X", "X_y") {
|
||||
checkUndo(1) {
|
||||
0.apply(Side.LEFT)
|
||||
}
|
||||
}
|
||||
|
||||
test1 ("X", "X", "X_y") {
|
||||
checkUndo(1) {
|
||||
0.apply(Side.RIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
test1 ("X_z", "X_y", "X") {
|
||||
checkUndo(1) {
|
||||
0.apply(Side.LEFT)
|
||||
}
|
||||
}
|
||||
|
||||
test1 ("X_z", "X_y", "X") {
|
||||
checkUndo(1) {
|
||||
0.apply(Side.RIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
test2 ("y_X", "X", "X_z") {
|
||||
checkUndo(1) {
|
||||
0.apply(Side.LEFT)
|
||||
}
|
||||
checkUndo(1) {
|
||||
1.apply(Side.RIGHT)
|
||||
}
|
||||
|
||||
assertContent("y_X_z")
|
||||
}
|
||||
}
|
||||
|
||||
public fun testRangeModification() {
|
||||
test1("X_x_y_z_Y", "X_a_b_c_Y", "X_x_y_z_Y") {
|
||||
0.assertContent("a_b_c", 1, 4)
|
||||
checkUndo(1) { replaceText(!2 - 0, !2 - 1, "D") }
|
||||
0.assertContent("a_D_c", 1, 4)
|
||||
}
|
||||
|
||||
test1("X_x_y_z_Y", "X_a_b_c_Y", "X_x_y_z_Y") {
|
||||
0.assertContent("a_b_c", 1, 4)
|
||||
checkUndo(1) { replaceText(!2 - -1, !2 - 2, "D") }
|
||||
0.assertContent("aDc", 1, 2)
|
||||
}
|
||||
|
||||
test1("X_x_y_z_Y", "X_a_b_c_Y", "X_x_y_z_Y") {
|
||||
0.assertContent("a_b_c", 1, 4)
|
||||
checkUndo(1) { replaceText("c", "u_x") }
|
||||
0.assertContent("a_b_u_x", 1, 5)
|
||||
}
|
||||
|
||||
test1("X_x_y_z_Y", "X_a_b_c_Y", "X_x_y_z_Y") {
|
||||
0.assertContent("a_b_c", 1, 4)
|
||||
checkUndo(1) { deleteText("c_") }
|
||||
0.assertContent("a_b", 1, 3)
|
||||
}
|
||||
|
||||
test1("X_x_y_z_Y", "X_a_b_c_Y", "X_x_y_z_Y") {
|
||||
0.assertContent("a_b_c", 1, 4)
|
||||
checkUndo(1) { insertTextBefore("b", "q") }
|
||||
0.assertContent("a_qb_c", 1, 4)
|
||||
}
|
||||
|
||||
test1("X_x_y_z_Y", "X_a_b_c_Y", "X_x_y_z_Y") {
|
||||
0.assertContent("a_b_c", 1, 4)
|
||||
checkUndo(1) { insertTextAfter("b", "q") }
|
||||
0.assertContent("a_bq_c", 1, 4)
|
||||
}
|
||||
|
||||
test1("X_x_y_z_Y", "X_a_b_c_Y", "X_x_y_z_Y") {
|
||||
0.assertContent("a_b_c", 1, 4)
|
||||
checkUndo(1) { insertText(0, "a_b_c_") }
|
||||
0.assertContent("a_b_c", 4, 7)
|
||||
}
|
||||
|
||||
test1("A_X_x_y_z_Y", "A_X_a_b_c_Y", "A_X_x_y_z_Y") {
|
||||
0.assertContent("a_b_c", 2, 5)
|
||||
checkUndo(1) { replaceText("X_a_b", "q_w_e") }
|
||||
0.assertContent("c")
|
||||
}
|
||||
|
||||
test1("X_x_y_z_Y_A", "X_a_b_c_Y_A", "X_x_y_z_Y_A") {
|
||||
0.assertContent("a_b_c")
|
||||
checkUndo(1) { replaceText("c_Y", "q") }
|
||||
0.assertContent("a_b")
|
||||
}
|
||||
|
||||
test1("A_X_x_Y_A", "A_X_b_Y_A", "A_X_x_z_Y_A") {
|
||||
0.assertContent("b")
|
||||
checkUndo(1) { replaceText("X_b_Y", "q") }
|
||||
|
||||
0.assertContent("", 2, 2)
|
||||
assertContent("A_q_A")
|
||||
}
|
||||
}
|
||||
|
||||
public fun testNonConflictsActions() {
|
||||
val applyAllTitle = DiffBundle.message("merge.dialog.apply.all.non.conflicting.changes.action.name")
|
||||
val applyLeftTitle = DiffBundle.message("merge.dialog.apply.left.non.conflicting.changes.action.name")
|
||||
val applyRightTitle = DiffBundle.message("merge.dialog.apply.right.non.conflicting.changes.action.name")
|
||||
|
||||
val text1 =
|
||||
"1 ======\n" +
|
||||
"insert left\n" +
|
||||
"2 ======\n" +
|
||||
"remove right\n" +
|
||||
"3 ======\n" +
|
||||
"new both\n" +
|
||||
"4 ======\n" +
|
||||
"modify both\n" +
|
||||
"5 ======\n" +
|
||||
"modify\n" +
|
||||
"6 ======\n" +
|
||||
"7 ======"
|
||||
val text2 =
|
||||
"1 ======\n" +
|
||||
"2 ======\n" +
|
||||
"remove right\n" +
|
||||
"3 ======\n" +
|
||||
"4 ======\n" +
|
||||
"modify\n" +
|
||||
"5 ======\n" +
|
||||
"modify\n" +
|
||||
"6 ======\n" +
|
||||
"delete modify\n" +
|
||||
"7 ======"
|
||||
val text3 =
|
||||
"1 ======\n" +
|
||||
"2 ======\n" +
|
||||
"3 ======\n" +
|
||||
"new both\n" +
|
||||
"4 ======\n" +
|
||||
"modify both\n" +
|
||||
"5 ======\n" +
|
||||
"modify right\n" +
|
||||
"6 ======\n" +
|
||||
"modify\n" +
|
||||
"7 ======"
|
||||
|
||||
testN(text1, text2, text3) {
|
||||
checkUndo(1) {
|
||||
runActionByTitle(applyAllTitle)
|
||||
}
|
||||
|
||||
assertChangesCount(1)
|
||||
assertContent(
|
||||
"1 ======\n" +
|
||||
"insert left\n" +
|
||||
"2 ======\n" +
|
||||
"3 ======\n" +
|
||||
"new both\n" +
|
||||
"4 ======\n" +
|
||||
"modify both\n" +
|
||||
"5 ======\n" +
|
||||
"modify right\n" +
|
||||
"6 ======\n" +
|
||||
"delete modify\n" +
|
||||
"7 ======")
|
||||
}
|
||||
|
||||
testN(text1, text2, text3) {
|
||||
checkUndo(1) {
|
||||
runActionByTitle(applyLeftTitle)
|
||||
}
|
||||
|
||||
assertChangesCount(3)
|
||||
assertContent(
|
||||
"1 ======\n" +
|
||||
"insert left\n" +
|
||||
"2 ======\n" +
|
||||
"remove right\n" +
|
||||
"3 ======\n" +
|
||||
"new both\n" +
|
||||
"4 ======\n" +
|
||||
"modify both\n" +
|
||||
"5 ======\n" +
|
||||
"modify\n" +
|
||||
"6 ======\n" +
|
||||
"delete modify\n" +
|
||||
"7 ======")
|
||||
}
|
||||
|
||||
testN(text1, text2, text3) {
|
||||
checkUndo(1) {
|
||||
runActionByTitle(applyRightTitle)
|
||||
}
|
||||
|
||||
assertChangesCount(2)
|
||||
assertContent(
|
||||
"1 ======\n" +
|
||||
"2 ======\n" +
|
||||
"3 ======\n" +
|
||||
"new both\n" +
|
||||
"4 ======\n" +
|
||||
"modify both\n" +
|
||||
"5 ======\n" +
|
||||
"modify right\n" +
|
||||
"6 ======\n" +
|
||||
"delete modify\n" +
|
||||
"7 ======")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.diff.merge
|
||||
|
||||
import com.intellij.diff.DiffContentFactoryImpl
|
||||
import com.intellij.diff.DiffTestCase
|
||||
import com.intellij.diff.DiffTestCase.Trio
|
||||
import com.intellij.diff.assertEquals
|
||||
import com.intellij.diff.assertTrue
|
||||
import com.intellij.diff.contents.DocumentContent
|
||||
import com.intellij.diff.merge.MergeTestBase.SidesState.BOTH
|
||||
import com.intellij.diff.merge.MergeTestBase.SidesState.LEFT
|
||||
import com.intellij.diff.merge.MergeTestBase.SidesState.NONE
|
||||
import com.intellij.diff.merge.MergeTestBase.SidesState.RIGHT
|
||||
import com.intellij.diff.merge.TextMergeTool.TextMergeViewer
|
||||
import com.intellij.diff.merge.TextMergeTool.TextMergeViewer.MyThreesideViewer
|
||||
import com.intellij.diff.util.DiffUtil
|
||||
import com.intellij.diff.util.Side
|
||||
import com.intellij.diff.util.TextDiffType
|
||||
import com.intellij.diff.util.ThreeSide
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.command.undo.UndoManager
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Couple
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.testFramework.fixtures.IdeaProjectTestFixture
|
||||
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
|
||||
import com.intellij.util.ui.UIUtil
|
||||
|
||||
public abstract class MergeTestBase : DiffTestCase() {
|
||||
private var projectFixture: IdeaProjectTestFixture? = null
|
||||
private var project: Project? = null
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
projectFixture = IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getTestName(true)).getFixture()
|
||||
projectFixture!!.setUp()
|
||||
project = projectFixture!!.getProject()
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
projectFixture?.tearDown()
|
||||
project = null
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
public fun test1(left: String, base: String, right: String, f: TestBuilder.() -> Unit) {
|
||||
test(left, base, right, 1, f)
|
||||
}
|
||||
|
||||
public fun test2(left: String, base: String, right: String, f: TestBuilder.() -> Unit) {
|
||||
test(left, base, right, 2, f)
|
||||
}
|
||||
|
||||
public fun testN(left: String, base: String, right: String, f: TestBuilder.() -> Unit) {
|
||||
test(left, base, right, -1, f)
|
||||
}
|
||||
|
||||
public fun test(left: String, base: String, right: String, changesCount: Int, f: TestBuilder.() -> Unit) {
|
||||
val contentFactory = DiffContentFactoryImpl()
|
||||
val leftContent: DocumentContent = contentFactory.create(parseSource(left))
|
||||
val baseContent: DocumentContent = contentFactory.create(parseSource(base))
|
||||
val rightContent: DocumentContent = contentFactory.create(parseSource(right))
|
||||
val outputContent: DocumentContent = contentFactory.create(parseSource(""))
|
||||
outputContent.getDocument().setReadOnly(false)
|
||||
|
||||
val context = MockMergeContext(project)
|
||||
val request = MockMergeRequest(leftContent, baseContent, rightContent, outputContent)
|
||||
|
||||
val viewer = TextMergeTool.INSTANCE.createComponent(context, request) as TextMergeViewer
|
||||
try {
|
||||
val toolbar = viewer.init()
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
|
||||
val builder = TestBuilder(viewer, toolbar.toolbarActions ?: emptyList())
|
||||
builder.assertChangesCount(changesCount)
|
||||
builder.f()
|
||||
} finally {
|
||||
Disposer.dispose(viewer)
|
||||
}
|
||||
}
|
||||
|
||||
public inner class TestBuilder(public val mergeViewer: TextMergeViewer, private val actions: List<AnAction>) {
|
||||
public val viewer: MyThreesideViewer = mergeViewer.getViewer()
|
||||
public val changes: List<TextMergeChange> = viewer.getAllChanges()
|
||||
public val editor: EditorEx = viewer.getEditor(ThreeSide.BASE)
|
||||
public val document: Document = editor.getDocument()
|
||||
|
||||
private val textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
|
||||
private val undoManager = UndoManager.getInstance(project!!)
|
||||
|
||||
public fun change(num: Int): TextMergeChange {
|
||||
if (changes.size() < num) throw Exception("changes: ${changes.size()}, index: $num")
|
||||
return changes.get(num)
|
||||
}
|
||||
|
||||
public fun activeChanges(): List<TextMergeChange> = viewer.getChanges()
|
||||
|
||||
//
|
||||
// Actions
|
||||
//
|
||||
|
||||
public fun runActionByTitle(name: String): Boolean {
|
||||
val action = actions.filter { name.equals(it.getTemplatePresentation().getText()) }
|
||||
assertTrue(action.size() == 1, action.toString())
|
||||
return runAction(action.get(0))
|
||||
}
|
||||
|
||||
private fun runAction(action: AnAction): Boolean {
|
||||
val actionEvent = AnActionEvent.createFromAnAction(action, null, ActionPlaces.MAIN_MENU, editor.getDataContext())
|
||||
action.update(actionEvent)
|
||||
val success = actionEvent.getPresentation().isEnabledAndVisible()
|
||||
if (success) action.actionPerformed(actionEvent)
|
||||
return success
|
||||
}
|
||||
|
||||
//
|
||||
// Modification
|
||||
//
|
||||
|
||||
public fun command(affected: TextMergeChange, f: () -> Unit): Unit {
|
||||
command(listOf(affected), f)
|
||||
}
|
||||
|
||||
public fun command(affected: List<TextMergeChange>? = null, f: () -> Unit): Unit {
|
||||
viewer.executeMergeCommand(null, affected, f)
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
}
|
||||
|
||||
public fun write(f: () -> Unit): Unit {
|
||||
ApplicationManager.getApplication().runWriteAction({ CommandProcessor.getInstance().executeCommand(project, f, null, null) })
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
}
|
||||
|
||||
public fun Int.ignore(side: Side, modifier: Boolean = false) {
|
||||
val change = change(this)
|
||||
command(change) { viewer.ignoreChange(change, side, modifier) }
|
||||
}
|
||||
|
||||
public fun Int.apply(side: Side, modifier: Boolean = false) {
|
||||
val change = change(this)
|
||||
command(change) { viewer.replaceChange(change, side, modifier) }
|
||||
}
|
||||
|
||||
//
|
||||
// Text modification
|
||||
//
|
||||
|
||||
public fun insertText(offset: Int, newContent: CharSequence) {
|
||||
replaceText(offset, offset, newContent)
|
||||
}
|
||||
|
||||
public fun deleteText(startOffset: Int, endOffset: Int) {
|
||||
replaceText(startOffset, endOffset, "")
|
||||
}
|
||||
|
||||
public fun replaceText(startOffset: Int, endOffset: Int, newContent: CharSequence) {
|
||||
write { document.replaceString(startOffset, endOffset, parseSource(newContent)) }
|
||||
}
|
||||
|
||||
public fun insertText(offset: LineCol, newContent: CharSequence) {
|
||||
replaceText(offset.toOffset(), offset.toOffset(), newContent)
|
||||
}
|
||||
|
||||
public fun deleteText(startOffset: LineCol, endOffset: LineCol) {
|
||||
replaceText(startOffset.toOffset(), endOffset.toOffset(), "")
|
||||
}
|
||||
|
||||
public fun replaceText(startOffset: LineCol, endOffset: LineCol, newContent: CharSequence) {
|
||||
write { replaceText(startOffset.toOffset(), endOffset.toOffset(), newContent) }
|
||||
}
|
||||
|
||||
public fun replaceText(oldContent: CharSequence, newContent: CharSequence) {
|
||||
write {
|
||||
val range = findRange(parseSource(oldContent))
|
||||
replaceText(range.first, range.second, newContent)
|
||||
}
|
||||
}
|
||||
|
||||
public fun deleteText(oldContent: CharSequence) {
|
||||
write {
|
||||
val range = findRange(parseSource(oldContent))
|
||||
replaceText(range.first, range.second, "")
|
||||
}
|
||||
}
|
||||
|
||||
public fun insertTextBefore(oldContent: CharSequence, newContent: CharSequence) {
|
||||
write { insertText(findRange(parseSource(oldContent)).first, newContent) }
|
||||
}
|
||||
|
||||
public fun insertTextAfter(oldContent: CharSequence, newContent: CharSequence) {
|
||||
write { insertText(findRange(parseSource(oldContent)).second, newContent) }
|
||||
}
|
||||
|
||||
private fun findRange(oldContent: CharSequence): Couple<Int> {
|
||||
val text = document.getImmutableCharSequence()
|
||||
val index1 = StringUtil.indexOf(text, oldContent)
|
||||
assertTrue(index1 >= 0, "content - '\n$oldContent\n'\ntext - '\n$text'")
|
||||
val index2 = StringUtil.indexOf(text, oldContent, index1 + 1)
|
||||
assertTrue(index2 == -1, "content - '\n$oldContent\n'\ntext - '\n$text'")
|
||||
return Couple(index1, index1 + oldContent.length())
|
||||
}
|
||||
|
||||
//
|
||||
// Undo
|
||||
//
|
||||
|
||||
public fun undo(count: Int = 1) {
|
||||
if (count == -1) {
|
||||
while (undoManager.isUndoAvailable(textEditor)) {
|
||||
undoManager.undo(textEditor)
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i in 1..count) {
|
||||
assertTrue(undoManager.isUndoAvailable(textEditor))
|
||||
undoManager.undo(textEditor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun redo(count: Int = 1) {
|
||||
if (count == -1) {
|
||||
while (undoManager.isRedoAvailable(textEditor)) {
|
||||
undoManager.redo(textEditor)
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i in 1..count) {
|
||||
assertTrue(undoManager.isRedoAvailable(textEditor))
|
||||
undoManager.redo(textEditor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun checkUndo(count: Int = -1, f: TestBuilder.() -> Unit) {
|
||||
val initialState = ViewerState.recordState(viewer)
|
||||
f()
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
|
||||
val afterState = ViewerState.recordState(viewer)
|
||||
undo(count)
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
|
||||
val undoState = ViewerState.recordState(viewer)
|
||||
redo(count)
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
|
||||
val redoState = ViewerState.recordState(viewer)
|
||||
|
||||
assertEquals(initialState, undoState)
|
||||
assertEquals(afterState, redoState)
|
||||
}
|
||||
|
||||
//
|
||||
// Checks
|
||||
//
|
||||
|
||||
public fun assertChangesCount(expected: Int) {
|
||||
if (expected == -1) return
|
||||
val actual = activeChanges().size()
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
public fun Int.assertType(type: TextDiffType, changeType: SidesState) {
|
||||
assertType(type)
|
||||
assertType(changeType)
|
||||
}
|
||||
|
||||
public fun Int.assertType(type: TextDiffType) {
|
||||
val change = change(this)
|
||||
assertEquals(change.getDiffType(), type)
|
||||
}
|
||||
|
||||
public fun Int.assertType(changeType: SidesState) {
|
||||
assertTrue(changeType != NONE)
|
||||
val change = change(this)
|
||||
val actual = change.getType()
|
||||
val isLeftChange = changeType != RIGHT
|
||||
val isRightChange = changeType != LEFT
|
||||
assertEquals(Pair(isLeftChange, isRightChange), Pair(actual.isLeftChange(), actual.isRightChange()))
|
||||
}
|
||||
|
||||
public fun Int.assertResolved(type: SidesState) {
|
||||
val change = change(this)
|
||||
val isLeftResolved = type == LEFT || type == BOTH
|
||||
val isRightResolved = type == RIGHT || type == BOTH
|
||||
assertEquals(Pair(isLeftResolved, isRightResolved), Pair(change.isResolved(Side.LEFT), change.isResolved(Side.RIGHT)))
|
||||
}
|
||||
|
||||
public fun Int.assertRange(start: Int, end: Int) {
|
||||
val change = change(this)
|
||||
assertEquals(Pair(start, end), Pair(change.getStartLine(ThreeSide.BASE), change.getEndLine(ThreeSide.BASE)))
|
||||
}
|
||||
|
||||
public fun Int.assertContent(expected: String, start: Int, end: Int) {
|
||||
assertContent(expected)
|
||||
assertRange(start, end)
|
||||
}
|
||||
|
||||
public fun Int.assertContent(expected: String) {
|
||||
val change = change(this)
|
||||
val document = editor.getDocument()
|
||||
val actual = DiffUtil.getLinesContent(document, change.getStartLine(ThreeSide.BASE), change.getEndLine(ThreeSide.BASE))
|
||||
assertEquals(parseSource(expected), actual)
|
||||
}
|
||||
|
||||
public fun assertContent(expected: String) {
|
||||
val actual = viewer.getEditor(ThreeSide.BASE).getDocument().getImmutableCharSequence()
|
||||
assertEquals(parseSource(expected), actual)
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
public fun Int.not(): LineColHelper = LineColHelper(this)
|
||||
public fun LineColHelper.minus(col: Int): LineCol = LineCol(this.line, col)
|
||||
|
||||
public inner class LineColHelper(val line: Int) {
|
||||
}
|
||||
|
||||
public inner data class LineCol(val line: Int, val col: Int) {
|
||||
public fun toOffset(): Int = editor.getDocument().getLineStartOffset(line) + col
|
||||
}
|
||||
}
|
||||
|
||||
private class MockMergeContext(private val myProject: Project?) : MergeContext() {
|
||||
override fun getProject(): Project? = myProject
|
||||
|
||||
override fun isFocused(): Boolean = false
|
||||
|
||||
override fun requestFocus() {
|
||||
}
|
||||
|
||||
override fun finishMerge(result: MergeResult) {
|
||||
}
|
||||
}
|
||||
|
||||
private class MockMergeRequest(val left: DocumentContent,
|
||||
val base: DocumentContent,
|
||||
val right: DocumentContent,
|
||||
val output: DocumentContent) : TextMergeRequest() {
|
||||
override fun getTitle(): String? = null
|
||||
|
||||
override fun applyResult(result: MergeResult) {
|
||||
}
|
||||
|
||||
override fun getContents(): List<DocumentContent> = listOf(left, base, right)
|
||||
|
||||
override fun getOutputContent(): DocumentContent = output
|
||||
|
||||
override fun getContentTitles(): List<String?> = listOf(null, null, null)
|
||||
}
|
||||
|
||||
public enum class SidesState {
|
||||
LEFT, RIGHT, BOTH, NONE
|
||||
}
|
||||
|
||||
private data class ViewerState private constructor(private val content: CharSequence,
|
||||
private val changes: List<ViewerState.ChangeState>) {
|
||||
companion object {
|
||||
public fun recordState(viewer: MyThreesideViewer): ViewerState {
|
||||
val content = viewer.getEditor(ThreeSide.BASE).getDocument().getImmutableCharSequence()
|
||||
val changes = viewer.getAllChanges().map { recordChangeState(viewer, it) }
|
||||
return ViewerState(content, changes)
|
||||
}
|
||||
|
||||
private fun recordChangeState(viewer: MyThreesideViewer, change: TextMergeChange): ChangeState {
|
||||
val document = viewer.getEditor(ThreeSide.BASE).getDocument();
|
||||
val content = DiffUtil.getLinesContent(document, change.getStartLine(ThreeSide.BASE), change.getEndLine(ThreeSide.BASE))
|
||||
|
||||
val resolved = if (change.isResolved()) BOTH else if (change.isResolved(Side.LEFT)) LEFT else if (change.isResolved(Side.RIGHT)) RIGHT else NONE
|
||||
|
||||
val starts = Trio.from { change.getStartLine(it) }
|
||||
val ends = Trio.from { change.getStartLine(it) }
|
||||
|
||||
return ChangeState(content, starts, ends, resolved)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this identityEquals other) return true
|
||||
if (other !is ViewerState) return false
|
||||
|
||||
if (!StringUtil.equals(content, other.content)) return false
|
||||
if (!changes.equals(other.changes)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = StringUtil.hashCode(content)
|
||||
|
||||
private data class ChangeState(private val content: CharSequence,
|
||||
private val starts: Trio<Int>,
|
||||
private val ends: Trio<Int>,
|
||||
private val resolved: SidesState) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this identityEquals other) return true
|
||||
if (other !is ChangeState) return false
|
||||
|
||||
if (!StringUtil.equals(content, other.content)) return false
|
||||
if (!starts.equals(other.starts)) return false
|
||||
if (!ends.equals(other.ends)) return false
|
||||
if (!resolved.equals(other.resolved)) return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -179,12 +179,12 @@ public class LineNumberConvertorCorrectorTest : UsefulTestCase() {
|
||||
public fun printMatchings() {
|
||||
for (i in 0..length * 2 - 1) {
|
||||
val value = convertor.convert1(i)
|
||||
if (value != -1) println("L: " + i + " - " + value)
|
||||
if (value != -1) println("L: $i - $value")
|
||||
}
|
||||
|
||||
for (i in 0..length * 2 - 1) {
|
||||
val value = convertor.convert2(i)
|
||||
if (value != -1) println("R: " + i + " - " + value)
|
||||
if (value != -1) println("R: $i - $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-51
@@ -15,10 +15,9 @@
|
||||
*/
|
||||
package com.intellij.diff.tools.fragmented
|
||||
|
||||
import com.intellij.diff.DiffTestCase
|
||||
import com.intellij.diff.assertEquals
|
||||
import com.intellij.diff.assertTrue
|
||||
import com.intellij.diff.comparison.AutoTestCase
|
||||
import com.intellij.diff.comparison.ComparisonManagerImpl
|
||||
import com.intellij.diff.comparison.ComparisonPolicy
|
||||
import com.intellij.diff.util.LineRange
|
||||
import com.intellij.diff.util.Side
|
||||
@@ -28,48 +27,28 @@ import com.intellij.openapi.progress.DumbProgressIndicator
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.util.containers.HashMap
|
||||
|
||||
public class UnifiedFragmentBuilderAutoTest : AutoTestCase() {
|
||||
public class UnifiedFragmentBuilderAutoTest : DiffTestCase() {
|
||||
public fun test() {
|
||||
doTest(System.currentTimeMillis(), 30, 30)
|
||||
}
|
||||
|
||||
public fun doTest(seed: Long, runs: Int, maxLength: Int) {
|
||||
RNG.setSeed(seed)
|
||||
doAutoTest(seed, runs) { debugData ->
|
||||
debugData.put("MaxLength", maxLength)
|
||||
|
||||
var policy: ComparisonPolicy? = null
|
||||
var masterSide: Side? = null
|
||||
var lastSeed: Long = -1;
|
||||
var text1 = DocumentImpl(generateText(maxLength))
|
||||
var text2 = DocumentImpl(generateText(maxLength))
|
||||
|
||||
for (i in 1..runs) {
|
||||
if (i % 1000 == 0) println(i)
|
||||
var text1: Document? = null
|
||||
var text2: Document? = null
|
||||
try {
|
||||
lastSeed = getCurrentSeed()
|
||||
debugData.put("Text1", textToReadableFormat(text1.getCharsSequence()))
|
||||
debugData.put("Text2", textToReadableFormat(text2.getCharsSequence()))
|
||||
|
||||
text1 = generateText(maxLength)
|
||||
text2 = generateText(maxLength)
|
||||
|
||||
for (side in Side.values()) {
|
||||
for (comparisonPolicy in ComparisonPolicy.values()) {
|
||||
policy = comparisonPolicy
|
||||
masterSide = side
|
||||
doTest(text1, text2, policy, masterSide)
|
||||
}
|
||||
for (side in Side.values()) {
|
||||
for (comparisonPolicy in ComparisonPolicy.values()) {
|
||||
debugData.put("Policy", comparisonPolicy)
|
||||
debugData.put("Current side", side)
|
||||
doTest(text1, text2, comparisonPolicy, side)
|
||||
}
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
println("Seed: " + seed)
|
||||
println("Runs: " + runs)
|
||||
println("MaxLength: " + maxLength)
|
||||
println("Policy: " + policy!!)
|
||||
println("Current side: " + masterSide!!)
|
||||
println("I: " + i)
|
||||
println("Current seed: " + lastSeed)
|
||||
println("Text1: " + textToReadableFormat(text1?.getCharsSequence()))
|
||||
println("Text2: " + textToReadableFormat(text2?.getCharsSequence()))
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,8 +137,7 @@ public class UnifiedFragmentBuilderAutoTest : AutoTestCase() {
|
||||
if (c1 == c2) {
|
||||
index1++
|
||||
index2++
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
index1++
|
||||
}
|
||||
}
|
||||
@@ -173,19 +151,4 @@ public class UnifiedFragmentBuilderAutoTest : AutoTestCase() {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun generateText(maxLength: Int): Document {
|
||||
return DocumentImpl(generateText(maxLength, CHAR_COUNT, CHAR_TABLE))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val MANAGER = ComparisonManagerImpl()
|
||||
|
||||
private val CHAR_COUNT = 12
|
||||
private val CHAR_TABLE: Map<Int, Char> = {
|
||||
val map = HashMap<Int, Char>()
|
||||
listOf('\n', '\n', '\t', ' ', ' ', '.', '<', '!').forEachIndexed { i, c -> map.put(i, c) }
|
||||
map
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user