merge: tests

This commit is contained in:
Aleksey Pivovarov
2015-09-08 13:43:19 +03:00
parent 11b536d64c
commit 291698b483
14 changed files with 1823 additions and 279 deletions
@@ -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
}
}
}
}
@@ -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")
}
}
}
@@ -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
}()
}
}