[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / platform / diff-impl / tests / com / intellij / diff / comparison / ComparisonUtilTestBase.kt
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.diff.comparison
3
4 import com.intellij.diff.DiffTestCase
5 import com.intellij.diff.fragments.DiffFragment
6 import com.intellij.diff.fragments.LineFragment
7 import com.intellij.diff.fragments.MergeWordFragment
8 import com.intellij.diff.util.IntPair
9 import com.intellij.diff.util.ThreeSide
10 import com.intellij.openapi.editor.Document
11 import com.intellij.openapi.editor.impl.DocumentImpl
12 import com.intellij.openapi.util.Couple
13 import java.util.*
14
15 abstract class ComparisonUtilTestBase : DiffTestCase() {
16   private fun doLineTest(text: Couple<Document>, matchings: Couple<BitSet>?, expected: List<Couple<IntPair>>?, policy: ComparisonPolicy) {
17     val before = text.first
18     val after = text.second
19     val fragments = MANAGER.compareLines(before.charsSequence, after.charsSequence, policy, INDICATOR)
20     checkConsistency(fragments, before, after)
21     if (matchings != null) checkLineMatching(fragments, matchings)
22     if (expected != null) checkLineChanges(fragments, expected)
23   }
24
25   private fun doLineInnerTest(text: Couple<Document>, matchings: Couple<BitSet>?, expected: List<Couple<IntPair>>?, policy: ComparisonPolicy) {
26     val before = text.first
27     val after = text.second
28     val rawFragments = MANAGER.compareLinesInner(before.charsSequence, after.charsSequence, policy, INDICATOR)
29     val fragments = MANAGER.squash(rawFragments)
30     checkConsistencyLineInner(fragments, before, after)
31
32     val diffFragments = fragments[0].innerFragments!!
33     if (matchings != null) checkDiffMatching(diffFragments, matchings)
34     if (expected != null) checkDiffChanges(diffFragments, expected)
35   }
36
37   private fun doWordTest(text: Couple<Document>, matchings: Couple<BitSet>?, expected: List<Couple<IntPair>>?, policy: ComparisonPolicy) {
38     val before = text.first
39     val after = text.second
40     val fragments = MANAGER.compareWords(before.charsSequence, after.charsSequence, policy, INDICATOR)
41     checkConsistency(fragments, before, after)
42
43     if (matchings != null) checkDiffMatching(fragments, matchings)
44     if (expected != null) checkDiffChanges(fragments, expected)
45   }
46
47   private fun doWordTest(text: Trio<Document>, matchings: Trio<BitSet>?, expected: List<Trio<IntPair>>?, policy: ComparisonPolicy) {
48     val before = text.data1
49     val base = text.data2
50     val after = text.data3
51     val fragments = ByWord.compare(before.charsSequence, base.charsSequence, after.charsSequence, policy, INDICATOR)
52     checkConsistency(fragments)
53
54     if (matchings != null) checkMergeMatching(fragments, matchings)
55     if (expected != null) checkMergeChanges(fragments, expected)
56   }
57
58   private fun doCharTest(text: Couple<Document>, matchings: Couple<BitSet>?, expected: List<Couple<IntPair>>?, policy: ComparisonPolicy) {
59     val before = text.first
60     val after = text.second
61     val fragments = MANAGER.compareChars(before.charsSequence, after.charsSequence, policy, INDICATOR)
62     checkConsistency(fragments, before, after)
63     if (matchings != null) checkDiffMatching(fragments, matchings)
64     if (expected != null) checkDiffChanges(fragments, expected)
65   }
66
67   private fun doCharRawTest(text: Couple<Document>, matchings: Couple<BitSet>?, expected: List<Couple<IntPair>>?) {
68     val before = text.first
69     val after = text.second
70     val iterable = ByChar.compare(before.charsSequence, after.charsSequence, INDICATOR)
71     val fragments = ComparisonManagerImpl.convertIntoDiffFragments(iterable)
72     checkConsistency(fragments, before, after)
73     if (matchings != null) checkDiffMatching(fragments, matchings)
74     if (expected != null) checkDiffChanges(fragments, expected)
75   }
76
77   private fun doSplitterTest(text: Couple<Document>,
78                              squash: Boolean,
79                              trim: Boolean,
80                              expected: List<Couple<IntPair>>?,
81                              policy: ComparisonPolicy) {
82     val before = text.first
83     val after = text.second
84     val text1 = before.charsSequence
85     val text2 = after.charsSequence
86
87     var fragments = MANAGER.compareLinesInner(text1, text2, policy, INDICATOR)
88     checkConsistency(fragments, before, after)
89
90     fragments = MANAGER.processBlocks(fragments, text1, text2, policy, squash, trim)
91     checkConsistency(fragments, before, after)
92
93     if (expected != null) checkLineChanges(fragments, expected)
94   }
95
96   private fun checkConsistencyLineInner(fragments: List<LineFragment>, before: Document, after: Document) {
97     assertTrue(fragments.size == 1)
98     val fragment = fragments[0]
99
100     assertTrue(fragment.startOffset1 == 0)
101     assertTrue(fragment.startOffset2 == 0)
102     assertTrue(fragment.endOffset1 == before.textLength)
103     assertTrue(fragment.endOffset2 == after.textLength)
104
105     // It could be null if there are no common words. We do not test such cases here.
106     checkConsistency(fragment.innerFragments!!, before, after)
107   }
108
109   private fun checkConsistency(fragments: List<DiffFragment>, before: Document, after: Document) {
110     for (fragment in fragments) {
111       assertTrue(fragment.startOffset1 <= fragment.endOffset1)
112       assertTrue(fragment.startOffset2 <= fragment.endOffset2)
113
114       if (fragment is LineFragment) {
115         assertTrue(fragment.startLine1 <= fragment.endLine1)
116         assertTrue(fragment.startLine2 <= fragment.endLine2)
117
118         assertTrue(fragment.startLine1 != fragment.endLine1 || fragment.startLine2 != fragment.endLine2)
119
120         assertTrue(fragment.startLine1 >= 0)
121         assertTrue(fragment.startLine2 >= 0)
122         assertTrue(fragment.endLine1 <= getLineCount(before))
123         assertTrue(fragment.endLine2 <= getLineCount(after))
124
125         checkLineOffsets(fragment, before, after)
126
127         val innerFragments = fragment.innerFragments
128         innerFragments?.let { checkConsistency(innerFragments, before, after) }
129       }
130       else {
131         assertTrue(fragment.startOffset1 != fragment.endOffset1 || fragment.startOffset2 != fragment.endOffset2)
132       }
133     }
134   }
135
136   private fun checkConsistency(fragments: List<MergeWordFragment>) {
137     for (fragment in fragments) {
138       assertTrue(fragment.getStartOffset(ThreeSide.LEFT) <= fragment.getEndOffset(ThreeSide.LEFT))
139       assertTrue(fragment.getStartOffset(ThreeSide.BASE) <= fragment.getEndOffset(ThreeSide.BASE))
140       assertTrue(fragment.getStartOffset(ThreeSide.RIGHT) <= fragment.getEndOffset(ThreeSide.RIGHT))
141
142       assertTrue(fragment.getStartOffset(ThreeSide.LEFT) != fragment.getEndOffset(ThreeSide.LEFT) ||
143                  fragment.getStartOffset(ThreeSide.BASE) != fragment.getEndOffset(ThreeSide.BASE) ||
144                  fragment.getStartOffset(ThreeSide.RIGHT) != fragment.getEndOffset(ThreeSide.RIGHT))
145     }
146   }
147
148   private fun checkLineOffsets(fragment: LineFragment, before: Document, after: Document) {
149     checkLineOffsets(before, fragment.startLine1, fragment.endLine1, fragment.startOffset1, fragment.endOffset1)
150
151     checkLineOffsets(after, fragment.startLine2, fragment.endLine2, fragment.startOffset2, fragment.endOffset2)
152   }
153
154   private fun checkLineOffsets(document: Document, startLine: Int, endLine: Int, startOffset: Int, endOffset: Int) {
155     if (startLine != endLine) {
156       assertEquals(document.getLineStartOffset(startLine), startOffset)
157       var offset = document.getLineEndOffset(endLine - 1)
158       if (offset < document.textLength) offset++
159       assertEquals(offset, endOffset)
160     }
161     else {
162       val offset = if (startLine == getLineCount(document)) document.textLength else document.getLineStartOffset(startLine)
163       assertEquals(offset, startOffset)
164       assertEquals(offset, endOffset)
165     }
166   }
167
168   //
169   // Test Builder
170   //
171
172   internal enum class TestType {
173     LINE, LINE_INNER, WORD, CHAR, CHAR_SMART, CHAR_RAW, SPLITTER
174   }
175
176   internal inner class TestBuilder(private val type: TestType) {
177     private var isExecuted: Boolean = false
178
179     private var text: Data<Document> = Data()
180     private var changes: PolicyData<List<Data<IntPair>>> = PolicyData()
181     private var matchings: PolicyData<Data<BitSet>> = PolicyData()
182
183     private var shouldSquash: Boolean = false
184     private var shouldTrim: Boolean = false
185
186     fun assertExecuted() {
187       assertTrue(isExecuted)
188     }
189
190     private fun run(policy: ComparisonPolicy) {
191       try {
192         isExecuted = true
193
194         if (text.isTwoSide()) {
195           val text = text.asCouple()
196           val changes = changes.get(policy)?.map { it.asCouple() }
197           val matchings = matchings.get(policy)?.asCouple()
198           assertTrue(changes != null || matchings != null)
199
200           when (type) {
201             TestType.LINE -> doLineTest(text, matchings, changes, policy)
202             TestType.LINE_INNER -> {
203               doLineInnerTest(text, matchings, changes, policy)
204               doWordTest(text, matchings, changes, policy)
205             }
206             TestType.WORD -> doWordTest(text, matchings, changes, policy)
207             TestType.CHAR -> {
208               doCharTest(text, matchings, changes, policy)
209               if (policy == ComparisonPolicy.DEFAULT) doCharRawTest(text, matchings, changes)
210             }
211             TestType.CHAR_SMART -> {
212               doCharTest(text, matchings, changes, policy)
213             }
214             TestType.CHAR_RAW -> {
215               if (policy == ComparisonPolicy.DEFAULT) doCharRawTest(text, matchings, changes)
216             }
217             TestType.SPLITTER -> {
218               assertNull(matchings)
219               doSplitterTest(text, shouldSquash, shouldTrim, changes, policy)
220             }
221             else -> assert(false)
222           }
223         }
224         else {
225           val text = text.asTrio()
226           val changes = changes.get(policy)?.map { it.asTrio() }
227           val matchings = matchings.get(policy)?.asTrio()
228           assertTrue(changes != null || matchings != null)
229
230           when (type) {
231             TestType.WORD -> doWordTest(text, matchings, changes, policy)
232             else -> assert(false)
233           }
234         }
235       }
236       catch (e: Throwable) {
237         println("Policy: " + policy.name)
238         throw e
239       }
240     }
241
242
243     fun testAll() {
244       testDefault()
245       testTrim()
246       testIgnore()
247     }
248
249     fun testDefault() {
250       run(ComparisonPolicy.DEFAULT)
251     }
252
253     fun testTrim() {
254       run(ComparisonPolicy.TRIM_WHITESPACES)
255     }
256
257     fun testIgnore() {
258       run(ComparisonPolicy.IGNORE_WHITESPACES)
259     }
260
261
262     operator fun String.minus(v: String): Helper {
263       return Helper(this, v)
264     }
265
266     operator fun Helper.minus(v: String): Helper {
267       return Helper(before, v, after)
268     }
269
270     inner class Helper(val before: String, val after: String, val base: String? = null) {
271       init {
272         val builder = this@TestBuilder
273         if (builder.text.before == null && builder.text.after == null ||
274             base != null && builder.text.base == null) {
275           builder.text.before = DocumentImpl(parseSource(before))
276           builder.text.after = DocumentImpl(parseSource(after))
277           if (base != null) builder.text.base = DocumentImpl(parseSource(base))
278         }
279       }
280
281       fun plainSource() {
282         val builder = this@TestBuilder
283         builder.text.before = DocumentImpl(before)
284         builder.text.after = DocumentImpl(after)
285         if (base != null) {
286           builder.text.base = DocumentImpl(base)
287         }
288       }
289
290       fun default() {
291         assertNull(matchings.default)
292         matchings.default = parseMatching(before, after, base)
293       }
294
295       fun trim() {
296         assertNull(matchings.trim)
297         matchings.trim = parseMatching(before, after, base)
298       }
299
300       fun ignore() {
301         assertNull(matchings.ignore)
302         matchings.ignore = parseMatching(before, after, base)
303       }
304
305       private fun parseMatching(before: String, after: String, base: String?): Data<BitSet> {
306         val builder = this@TestBuilder
307         if (type == TestType.LINE) {
308           return Data(parseLineMatching(before, builder.text.before!!),
309                       if (base != null) parseLineMatching(base, builder.text.base!!) else null,
310                       parseLineMatching(after, builder.text.after!!))
311         }
312         else {
313           return Data(parseMatching(before, builder.text.before!!),
314                       if (base != null) parseMatching(base, builder.text.base!!) else null,
315                       parseMatching(after, builder.text.after!!))
316         }
317       }
318     }
319
320
321     fun default(vararg expected: Couple<IntPair>) {
322       assertNull(changes.default)
323       changes.default = listOf(*expected).map { Data(it.first, it.second) }
324     }
325
326     fun trim(vararg expected: Couple<IntPair>) {
327       assertNull(changes.trim)
328       changes.trim = listOf(*expected).map { Data(it.first, it.second) }
329     }
330
331     fun ignore(vararg expected: Couple<IntPair>) {
332       assertNull(changes.ignore)
333       changes.ignore = listOf(*expected).map { Data(it.first, it.second) }
334     }
335
336
337     fun postprocess(squash: Boolean, trim: Boolean) {
338       shouldSquash = squash
339       shouldTrim = trim
340     }
341   }
342
343   internal fun lines(f: TestBuilder.() -> Unit): Unit = doTest(TestType.LINE, f)
344
345   internal fun lines_inner(f: TestBuilder.() -> Unit): Unit = doTest(TestType.LINE_INNER, f)
346
347   internal fun words(f: TestBuilder.() -> Unit): Unit = doTest(TestType.WORD, f)
348
349   internal fun chars(f: TestBuilder.() -> Unit): Unit = doTest(TestType.CHAR, f)
350
351   internal fun chars_raw(f: TestBuilder.() -> Unit): Unit = doTest(TestType.CHAR_RAW, f)
352
353   internal fun chars_smart(f: TestBuilder.() -> Unit): Unit = doTest(TestType.CHAR_SMART, f)
354
355   internal fun splitter(squash: Boolean = false, trim: Boolean = false, f: TestBuilder.() -> Unit) {
356     doTest(TestType.SPLITTER) {
357       postprocess(squash, trim)
358       f()
359     }
360   }
361
362   private fun doTest(type: TestType, f: TestBuilder.() -> Unit) {
363     val builder = TestBuilder(type)
364     builder.f()
365     builder.assertExecuted()
366   }
367
368   //
369   // Helpers
370   //
371
372   private data class Data<T>(var before: T?, var base: T?, var after: T?) {
373     constructor() : this(null, null, null)
374     constructor(before: T?, after : T?) : this(before, null, after)
375     fun isTwoSide(): Boolean = before != null && after != null && base == null
376     fun isThreeSide(): Boolean = before != null && after != null && base != null
377     fun asCouple(): Couple<T> {
378       assert(isTwoSide())
379       return Couple(before!!, after!!)
380     }
381
382     fun asTrio(): Trio<T> {
383       assert(isThreeSide())
384       return Trio(before!!, base!!, after!!)
385     }
386   }
387
388   private data class PolicyData<T>(var default: T? = null, var trim: T? = null, var ignore: T? = null) {
389     fun get(policy: ComparisonPolicy): T? =
390       when (policy) {
391         ComparisonPolicy.IGNORE_WHITESPACES -> ignore ?: trim ?: default
392         ComparisonPolicy.TRIM_WHITESPACES -> trim ?: default
393         ComparisonPolicy.DEFAULT -> default
394       }
395   }
396
397   companion object {
398     fun checkLineChanges(fragments: List<LineFragment>, expected: List<Couple<IntPair>>) {
399       val changes = convertLineFragments(fragments)
400       assertOrderedEquals(expected, changes)
401     }
402
403     fun checkDiffChanges(fragments: List<DiffFragment>, expected: List<Couple<IntPair>>) {
404       val changes = convertDiffFragments(fragments)
405       assertOrderedEquals(expected, changes)
406     }
407
408     fun checkMergeChanges(fragments: List<MergeWordFragment>, expected: List<Trio<IntPair>>) {
409       val changes = convertMergeFragments(fragments)
410       assertOrderedEquals(expected, changes)
411     }
412
413     fun checkLineMatching(fragments: List<LineFragment>, matchings: Couple<BitSet>) {
414       val set1 = BitSet()
415       val set2 = BitSet()
416       for (fragment in fragments) {
417         set1.set(fragment.startLine1, fragment.endLine1)
418         set2.set(fragment.startLine2, fragment.endLine2)
419       }
420
421       assertSetsEquals(matchings.first, set1, "Before")
422       assertSetsEquals(matchings.second, set2, "After")
423     }
424
425     fun checkDiffMatching(fragments: List<DiffFragment>, matchings: Couple<BitSet>) {
426       val set1 = BitSet()
427       val set2 = BitSet()
428       for (fragment in fragments) {
429         set1.set(fragment.startOffset1, fragment.endOffset1)
430         set2.set(fragment.startOffset2, fragment.endOffset2)
431       }
432
433       assertSetsEquals(matchings.first, set1, "Before")
434       assertSetsEquals(matchings.second, set2, "After")
435     }
436
437     fun checkMergeMatching(fragments: List<MergeWordFragment>, matchings: Trio<BitSet>) {
438       val set1 = BitSet()
439       val set2 = BitSet()
440       val set3 = BitSet()
441       for (fragment in fragments) {
442         set1.set(fragment.getStartOffset(ThreeSide.LEFT), fragment.getEndOffset(ThreeSide.LEFT))
443         set2.set(fragment.getStartOffset(ThreeSide.BASE), fragment.getEndOffset(ThreeSide.BASE))
444         set3.set(fragment.getStartOffset(ThreeSide.RIGHT), fragment.getEndOffset(ThreeSide.RIGHT))
445       }
446
447       assertSetsEquals(matchings.data1, set1, "Left")
448       assertSetsEquals(matchings.data2, set2, "Base")
449       assertSetsEquals(matchings.data3, set3, "Right")
450     }
451
452     fun convertDiffFragments(fragments: List<DiffFragment>): List<Couple<IntPair>> {
453       return fragments.map { Couple(IntPair(it.startOffset1, it.endOffset1), IntPair(it.startOffset2, it.endOffset2)) }
454     }
455
456     fun convertLineFragments(fragments: List<LineFragment>): List<Couple<IntPair>> {
457       return fragments.map { Couple(IntPair(it.startLine1, it.endLine1), IntPair(it.startLine2, it.endLine2)) }
458     }
459
460     fun convertMergeFragments(fragments: List<MergeWordFragment>): List<Trio<IntPair>> {
461       return fragments.map {
462         Trio(IntPair(it.getStartOffset(ThreeSide.LEFT), it.getEndOffset(ThreeSide.LEFT)),
463              IntPair(it.getStartOffset(ThreeSide.BASE), it.getEndOffset(ThreeSide.BASE)),
464              IntPair(it.getStartOffset(ThreeSide.RIGHT), it.getEndOffset(ThreeSide.RIGHT)))
465       }
466     }
467
468
469     fun mod(line1: Int, line2: Int, count1: Int, count2: Int): Couple<IntPair> {
470       assert(count1 != 0)
471       assert(count2 != 0)
472       return Couple(IntPair(line1, line1 + count1), IntPair(line2, line2 + count2))
473     }
474
475     fun del(line1: Int, line2: Int, count1: Int): Couple<IntPair> {
476       assert(count1 != 0)
477       return Couple(IntPair(line1, line1 + count1), IntPair(line2, line2))
478     }
479
480     fun ins(line1: Int, line2: Int, count2: Int): Couple<IntPair> {
481       assert(count2 != 0)
482       return Couple(IntPair(line1, line1), IntPair(line2, line2 + count2))
483     }
484   }
485 }