[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / platform / diff-impl / tests / com / intellij / diff / comparison / ComparisonUtilAutoTest.kt
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.diff.comparison
17
18 import com.intellij.diff.DiffTestCase
19 import com.intellij.diff.HeavyDiffTestCase
20 import com.intellij.diff.fragments.DiffFragment
21 import com.intellij.diff.fragments.LineFragment
22 import com.intellij.diff.fragments.MergeLineFragment
23 import com.intellij.diff.fragments.MergeWordFragment
24 import com.intellij.diff.tools.util.base.HighlightPolicy
25 import com.intellij.diff.tools.util.base.IgnorePolicy
26 import com.intellij.diff.tools.util.text.LineOffsetsUtil
27 import com.intellij.diff.util.DiffUtil
28 import com.intellij.diff.util.Range
29 import com.intellij.diff.util.ThreeSide
30 import com.intellij.openapi.editor.Document
31 import com.intellij.openapi.editor.impl.DocumentImpl
32 import com.intellij.openapi.util.Couple
33 import com.intellij.openapi.util.text.StringUtil
34 import com.intellij.openapi.vcs.ex.createRanges
35 import java.util.*
36
37 class ComparisonUtilAutoTest : HeavyDiffTestCase() {
38   val RUNS = 30
39   val MAX_LENGTH = 300
40
41   fun testChar() {
42     doTestChar(System.currentTimeMillis(), RUNS, MAX_LENGTH)
43   }
44
45   fun testWord() {
46     doTestWord(System.currentTimeMillis(), RUNS, MAX_LENGTH)
47   }
48
49   fun testLine() {
50     doTestLine(System.currentTimeMillis(), RUNS, MAX_LENGTH)
51   }
52
53   fun testLineSquashed() {
54     doTestLineSquashed(System.currentTimeMillis(), RUNS, MAX_LENGTH)
55   }
56
57   fun testLineTrimSquashed() {
58     doTestLineTrimSquashed(System.currentTimeMillis(), RUNS, MAX_LENGTH)
59   }
60
61   fun testExplicitBlocks() {
62     doTestExplicitBlocks(System.currentTimeMillis(), RUNS, MAX_LENGTH)
63   }
64
65   fun testMerge() {
66     doTestMerge(System.currentTimeMillis(), RUNS, MAX_LENGTH)
67   }
68
69   fun testThreeWayDiff() {
70     doTestThreeWayDiff(System.currentTimeMillis(), RUNS, MAX_LENGTH)
71   }
72
73   private fun doTestLine(seed: Long, runs: Int, maxLength: Int) {
74     val ignorePolicies = listOf(ComparisonPolicy.DEFAULT, ComparisonPolicy.TRIM_WHITESPACES, ComparisonPolicy.IGNORE_WHITESPACES)
75     val fragmentsPolicies = listOf(InnerFragmentsPolicy.WORDS, InnerFragmentsPolicy.CHARS)
76
77     doTest(seed, runs, maxLength, ignorePolicies, fragmentsPolicies) { text1, text2, ignorePolicy, fragmentsPolicy, debugData ->
78       val sequence1 = text1.charsSequence
79       val sequence2 = text2.charsSequence
80
81       val lineOffsets1 = LineOffsetsUtil.create(sequence1)
82       val lineOffsets2 = LineOffsetsUtil.create(sequence2)
83
84       val fragments = MANAGER.compareLinesInner(sequence1, sequence2, lineOffsets1, lineOffsets2, ignorePolicy, fragmentsPolicy, INDICATOR)
85       debugData.put("Fragments", fragments)
86
87       checkResultLine(text1, text2, fragments, ignorePolicy, true)
88     }
89   }
90
91   private fun doTestLineSquashed(seed: Long, runs: Int, maxLength: Int) {
92     val ignorePolicies = listOf(ComparisonPolicy.DEFAULT, ComparisonPolicy.TRIM_WHITESPACES, ComparisonPolicy.IGNORE_WHITESPACES)
93     val fragmentsPolicies = listOf(InnerFragmentsPolicy.WORDS, InnerFragmentsPolicy.CHARS)
94
95     doTest(seed, runs, maxLength, ignorePolicies, fragmentsPolicies) { text1, text2, ignorePolicy, fragmentsPolicy, debugData ->
96       val sequence1 = text1.charsSequence
97       val sequence2 = text2.charsSequence
98
99       val lineOffsets1 = LineOffsetsUtil.create(sequence1)
100       val lineOffsets2 = LineOffsetsUtil.create(sequence2)
101
102       val fragments = MANAGER.compareLinesInner(sequence1, sequence2, lineOffsets1, lineOffsets2, ignorePolicy, fragmentsPolicy, INDICATOR)
103       debugData.put("Fragments", fragments)
104
105       val squashedFragments = MANAGER.squash(fragments)
106       debugData.put("Squashed Fragments", squashedFragments)
107
108       checkResultLine(text1, text2, squashedFragments, ignorePolicy, false)
109     }
110   }
111
112   private fun doTestLineTrimSquashed(seed: Long, runs: Int, maxLength: Int) {
113     val ignorePolicies = listOf(ComparisonPolicy.DEFAULT, ComparisonPolicy.TRIM_WHITESPACES, ComparisonPolicy.IGNORE_WHITESPACES)
114     val fragmentsPolicies = listOf(InnerFragmentsPolicy.WORDS, InnerFragmentsPolicy.CHARS)
115
116     doTest(seed, runs, maxLength, ignorePolicies, fragmentsPolicies) { text1, text2, ignorePolicy, fragmentsPolicy, debugData ->
117       val sequence1 = text1.charsSequence
118       val sequence2 = text2.charsSequence
119
120       val lineOffsets1 = LineOffsetsUtil.create(sequence1)
121       val lineOffsets2 = LineOffsetsUtil.create(sequence2)
122
123       val fragments = MANAGER.compareLinesInner(sequence1, sequence2, lineOffsets1, lineOffsets2, ignorePolicy, fragmentsPolicy, INDICATOR)
124       debugData.put("Fragments", fragments)
125
126       val processed = MANAGER.processBlocks(fragments, sequence1, sequence2, ignorePolicy, true, true)
127       debugData.put("Processed Fragments", processed)
128
129       checkResultLine(text1, text2, processed, ignorePolicy, false)
130     }
131   }
132
133   private fun doTestChar(seed: Long, runs: Int, maxLength: Int) {
134     val ignorePolicies = listOf(ComparisonPolicy.DEFAULT, ComparisonPolicy.TRIM_WHITESPACES, ComparisonPolicy.IGNORE_WHITESPACES)
135
136     doTest(seed, runs, maxLength, ignorePolicies) { text1, text2, ignorePolicy, debugData ->
137       val sequence1 = text1.charsSequence
138       val sequence2 = text2.charsSequence
139
140       val fragments = MANAGER.compareChars(sequence1, sequence2, ignorePolicy, INDICATOR)
141       debugData.put("Fragments", fragments)
142
143       checkResultChar(sequence1, sequence2, fragments, ignorePolicy)
144     }
145   }
146
147   private fun doTestWord(seed: Long, runs: Int, maxLength: Int) {
148     val ignorePolicies = listOf(ComparisonPolicy.DEFAULT, ComparisonPolicy.TRIM_WHITESPACES, ComparisonPolicy.IGNORE_WHITESPACES)
149
150     doTest(seed, runs, maxLength, ignorePolicies) { text1, text2, ignorePolicy, debugData ->
151       val sequence1 = text1.charsSequence
152       val sequence2 = text2.charsSequence
153
154       val fragments = MANAGER.compareWords(sequence1, sequence2, ignorePolicy, INDICATOR)
155       debugData.put("Fragments", fragments)
156
157       checkResultWord(sequence1, sequence2, fragments, ignorePolicy)
158     }
159   }
160
161   private fun doTestExplicitBlocks(seed: Long, runs: Int, maxLength: Int) {
162     val ignorePolicies = listOf(IgnorePolicy.DEFAULT, IgnorePolicy.TRIM_WHITESPACES, IgnorePolicy.IGNORE_WHITESPACES, IgnorePolicy.IGNORE_WHITESPACES_CHUNKS)
163     val highlightPolicies = listOf(HighlightPolicy.BY_LINE, HighlightPolicy.BY_WORD, HighlightPolicy.BY_WORD_SPLIT)
164
165     doTest(seed, runs, maxLength) { text1, text2, debugData ->
166       for (highlightPolicy in highlightPolicies) {
167         for (ignorePolicy in ignorePolicies) {
168           debugData.put("HighlightPolicy", highlightPolicy)
169           debugData.put("IgnorePolicy", ignorePolicy)
170
171           val sequence1 = text1.charsSequence
172           val sequence2 = text2.charsSequence
173
174           val ranges = createRanges(sequence2, sequence1).map { Range(it.vcsLine1, it.vcsLine2, it.line1, it.line2) }
175           debugData.put("Ranges", ranges)
176
177           val fragments = compareExplicitBlocks(sequence1, sequence2, ranges, highlightPolicy, ignorePolicy)
178           debugData.put("Fragments", fragments)
179
180           checkResultLine(text1, text2, fragments, ignorePolicy.comparisonPolicy, !highlightPolicy.isShouldSquash)
181         }
182       }
183     }
184   }
185
186   private fun doTestThreeWayDiff(seed: Long, runs: Int, maxLength: Int) {
187     val policies = listOf(ComparisonPolicy.DEFAULT, ComparisonPolicy.TRIM_WHITESPACES, ComparisonPolicy.IGNORE_WHITESPACES)
188
189     doTest3(seed, runs, maxLength, policies) { text1, text2, text3, policy, debugData ->
190       val sequence1 = text1.charsSequence
191       val sequence2 = text2.charsSequence
192       val sequence3 = text3.charsSequence
193
194       val fragments = MANAGER.compareLines(sequence1, sequence2, sequence3, policy, INDICATOR)
195
196       val fineFragments = fragments.map { f ->
197         val chunk1 = DiffUtil.getLinesContent(text1, f.startLine1, f.endLine1)
198         val chunk2 = DiffUtil.getLinesContent(text2, f.startLine2, f.endLine2)
199         val chunk3 = DiffUtil.getLinesContent(text3, f.startLine3, f.endLine3)
200
201         val wordFragments = ByWord.compare(chunk1, chunk2, chunk3, policy, INDICATOR)
202         Pair(f, wordFragments)
203       }
204       debugData.put("Fragments", fineFragments)
205
206       checkResultMerge(text1, text2, text3, fineFragments, policy, false)
207     }
208   }
209
210   private fun doTestMerge(seed: Long, runs: Int, maxLength: Int) {
211     val policies = listOf(ComparisonPolicy.DEFAULT, ComparisonPolicy.TRIM_WHITESPACES, ComparisonPolicy.IGNORE_WHITESPACES)
212
213     doTest3(seed, runs, maxLength, policies) { text1, text2, text3, policy, debugData ->
214       val sequence1 = text1.charsSequence
215       val sequence2 = text2.charsSequence
216       val sequence3 = text3.charsSequence
217
218       val fragments = MANAGER.mergeLines(sequence1, sequence2, sequence3, policy, INDICATOR)
219
220       val fineFragments = fragments.map { f ->
221         val chunk1 = DiffUtil.getLinesContent(text1, f.startLine1, f.endLine1)
222         val chunk2 = DiffUtil.getLinesContent(text2, f.startLine2, f.endLine2)
223         val chunk3 = DiffUtil.getLinesContent(text3, f.startLine3, f.endLine3)
224
225         val wordFragments = ByWord.compare(chunk1, chunk2, chunk3, policy, INDICATOR)
226         Pair(f, wordFragments)
227       }
228       debugData.put("Fragments", fineFragments)
229
230       checkResultMerge(text1, text2, text3, fineFragments, policy, policy != ComparisonPolicy.DEFAULT)
231     }
232   }
233
234
235   private fun doTest(seed: Long, runs: Int, maxLength: Int,
236                      ignorePolicies: List<ComparisonPolicy>, fragmentsPolicies: List<InnerFragmentsPolicy>,
237                      test: (Document, Document, ComparisonPolicy, InnerFragmentsPolicy, DiffTestCase.DebugData) -> Unit) {
238     doTest(seed, runs, maxLength, ignorePolicies) { text1, text2, ignorePolicy, debugData ->
239       for (fragmentsPolicy in fragmentsPolicies) {
240         debugData.put("Inner Policy", fragmentsPolicy)
241         test(text1, text2, ignorePolicy, fragmentsPolicy, debugData)
242       }
243     }
244   }
245
246   private fun doTest(seed: Long, runs: Int, maxLength: Int,
247                      ignorePolicies: List<ComparisonPolicy>,
248                      test: (Document, Document, ComparisonPolicy, DiffTestCase.DebugData) -> Unit) {
249     doTest(seed, runs, maxLength) { text1, text2, debugData ->
250       for (ignorePolicy in ignorePolicies) {
251         debugData.put("Ignore Policy", ignorePolicy)
252         test(text1, text2, ignorePolicy, debugData)
253       }
254     }
255   }
256
257   private fun doTest(seed: Long, runs: Int, maxLength: Int,
258                      test: (Document, Document, DiffTestCase.DebugData) -> Unit) {
259     doAutoTest(seed, runs) { debugData ->
260       debugData.put("MaxLength", maxLength)
261
262       for (useHighSurrogates in listOf(true, false)) {
263         debugData.put("High Surrogates", useHighSurrogates)
264
265         val text1 = DocumentImpl(generateText(maxLength, useHighSurrogates))
266         val text2 = DocumentImpl(generateText(maxLength, useHighSurrogates))
267
268         debugData.put("Text1", textToReadableFormat(text1.charsSequence))
269         debugData.put("Text2", textToReadableFormat(text2.charsSequence))
270
271         test(text1, text2, debugData)
272       }
273     }
274   }
275
276   private fun doTest3(seed: Long, runs: Int, maxLength: Int, policies: List<ComparisonPolicy>,
277                       test: (Document, Document, Document, ComparisonPolicy, DiffTestCase.DebugData) -> Unit) {
278     doAutoTest(seed, runs) { debugData ->
279       debugData.put("MaxLength", maxLength)
280
281       for (useHighSurrogates in listOf(true, false)) {
282         debugData.put("High Surrogates", useHighSurrogates)
283
284         val text1 = DocumentImpl(generateText(maxLength, useHighSurrogates))
285         val text2 = DocumentImpl(generateText(maxLength, useHighSurrogates))
286         val text3 = DocumentImpl(generateText(maxLength, useHighSurrogates))
287
288         debugData.put("Text1", textToReadableFormat(text1.charsSequence))
289         debugData.put("Text2", textToReadableFormat(text2.charsSequence))
290         debugData.put("Text3", textToReadableFormat(text3.charsSequence))
291
292         for (comparisonPolicy in policies) {
293           debugData.put("Policy", comparisonPolicy)
294           test(text1, text2, text2, comparisonPolicy, debugData)
295         }
296       }
297     }
298   }
299
300   private fun checkResultLine(text1: Document, text2: Document, fragments: List<LineFragment>, policy: ComparisonPolicy, allowNonSquashed: Boolean) {
301     checkLineConsistency(text1, text2, fragments, allowNonSquashed)
302
303     for (fragment in fragments) {
304       if (fragment.innerFragments != null) {
305         val sequence1 = text1.subSequence(fragment.startOffset1, fragment.endOffset1)
306         val sequence2 = text2.subSequence(fragment.startOffset2, fragment.endOffset2)
307
308         checkResultWord(sequence1, sequence2, fragment.innerFragments!!, policy)
309       }
310     }
311
312     checkValidRanges(text1.charsSequence, text2.charsSequence, fragments, policy, true)
313     checkCantTrimLines(text1, text2, fragments, policy, allowNonSquashed)
314   }
315
316   private fun checkResultWord(text1: CharSequence, text2: CharSequence, fragments: List<DiffFragment>, policy: ComparisonPolicy) {
317     checkDiffConsistency(fragments)
318     checkValidRanges(text1, text2, fragments, policy, false)
319   }
320
321   private fun checkResultChar(text1: CharSequence, text2: CharSequence, fragments: List<DiffFragment>, policy: ComparisonPolicy) {
322     checkDiffConsistency(fragments)
323     checkValidRanges(text1, text2, fragments, policy, false)
324   }
325
326   private fun checkResultMerge(text1: Document,
327                                text2: Document,
328                                text3: Document,
329                                fragments: List<Pair<MergeLineFragment, List<MergeWordFragment>>>,
330                                policy: ComparisonPolicy,
331                                allowIgnoredBlocks: Boolean) {
332     val lineFragments = fragments.map { it.first }
333     checkLineConsistency3(text1, text2, text3, lineFragments, allowIgnoredBlocks)
334
335     checkValidRanges3(text1, text2, text3, lineFragments, policy)
336     if (!allowIgnoredBlocks) checkCantTrimLines3(text1, text2, text3, lineFragments, policy)
337
338     for (pair in fragments) {
339       val f = pair.first
340       val innerFragments = pair.second
341       val chunk1 = DiffUtil.getLinesContent(text1, f.startLine1, f.endLine1)
342       val chunk2 = DiffUtil.getLinesContent(text2, f.startLine2, f.endLine2)
343       val chunk3 = DiffUtil.getLinesContent(text3, f.startLine3, f.endLine3)
344
345       checkDiffConsistency3(innerFragments)
346       checkValidRanges3(chunk1, chunk2, chunk3, innerFragments, policy)
347     }
348   }
349
350   private fun checkLineConsistency(text1: Document, text2: Document, fragments: List<LineFragment>, allowNonSquashed: Boolean) {
351     var last1 = -1
352     var last2 = -1
353
354     for (fragment in fragments) {
355       val startOffset1 = fragment.startOffset1
356       val startOffset2 = fragment.startOffset2
357       val endOffset1 = fragment.endOffset1
358       val endOffset2 = fragment.endOffset2
359
360       val start1 = fragment.startLine1
361       val start2 = fragment.startLine2
362       val end1 = fragment.endLine1
363       val end2 = fragment.endLine2
364
365       assertTrue(startOffset1 >= 0)
366       assertTrue(startOffset2 >= 0)
367       assertTrue(endOffset1 <= text1.textLength)
368       assertTrue(endOffset2 <= text2.textLength)
369
370       assertTrue(start1 >= 0)
371       assertTrue(start2 >= 0)
372       assertTrue(end1 <= getLineCount(text1))
373       assertTrue(end2 <= getLineCount(text2))
374
375       assertTrue(startOffset1 <= endOffset1)
376       assertTrue(startOffset2 <= endOffset2)
377
378       assertTrue(start1 <= end1)
379       assertTrue(start2 <= end2)
380       assertTrue(start1 != end1 || start2 != end2)
381
382       assertTrue(allowNonSquashed || start1 != last1 || start2 != last2)
383
384       checkLineOffsets(fragment, text1, text2)
385
386       last1 = end1
387       last2 = end2
388     }
389   }
390
391   private fun checkLineConsistency3(text1: Document, text2: Document, text3: Document, fragments: List<MergeLineFragment>,
392                                     allowNonSquashed: Boolean) {
393     var last1 = -1
394     var last2 = -1
395     var last3 = -1
396
397     for (fragment in fragments) {
398       val start1 = fragment.startLine1
399       val start2 = fragment.startLine2
400       val start3 = fragment.startLine3
401       val end1 = fragment.endLine1
402       val end2 = fragment.endLine2
403       val end3 = fragment.endLine3
404
405       assertTrue(start1 >= 0)
406       assertTrue(start2 >= 0)
407       assertTrue(start3 >= 0)
408       assertTrue(end1 <= getLineCount(text1))
409       assertTrue(end2 <= getLineCount(text2))
410       assertTrue(end3 <= getLineCount(text3))
411
412       assertTrue(start1 <= end1)
413       assertTrue(start2 <= end2)
414       assertTrue(start3 <= end3)
415       assertTrue(start1 != end1 || start2 != end2 || start3 != end3)
416
417       assertTrue(allowNonSquashed || start1 != last1 || start2 != last2 || start3 != last3)
418
419       last1 = end1
420       last2 = end2
421       last3 = end3
422     }
423   }
424
425   private fun checkDiffConsistency(fragments: List<DiffFragment>) {
426     var last1 = -1
427     var last2 = -1
428
429     for (diffFragment in fragments) {
430       val start1 = diffFragment.startOffset1
431       val start2 = diffFragment.startOffset2
432       val end1 = diffFragment.endOffset1
433       val end2 = diffFragment.endOffset2
434
435       assertTrue(start1 <= end1)
436       assertTrue(start2 <= end2)
437       assertTrue(start1 != end1 || start2 != end2)
438
439       assertTrue(start1 != last1 || start2 != last2)
440
441       last1 = end1
442       last2 = end2
443     }
444   }
445
446   private fun checkDiffConsistency3(fragments: List<MergeWordFragment>) {
447     var last1 = -1
448     var last2 = -1
449     var last3 = -1
450
451     for (diffFragment in fragments) {
452       val start1 = diffFragment.startOffset1
453       val start2 = diffFragment.startOffset2
454       val start3 = diffFragment.startOffset3
455       val end1 = diffFragment.endOffset1
456       val end2 = diffFragment.endOffset2
457       val end3 = diffFragment.endOffset3
458
459       assertTrue(start1 <= end1)
460       assertTrue(start2 <= end2)
461       assertTrue(start3 <= end3)
462       assertTrue(start1 != end1 || start2 != end2 || start3 != end3)
463
464       assertTrue(start1 != last1 || start2 != last2 || start3 != last3)
465
466       last1 = end1
467       last2 = end2
468       last3 = end3
469     }
470   }
471
472   private fun checkLineOffsets(fragment: LineFragment, before: Document, after: Document) {
473     checkLineOffsets(before, fragment.startLine1, fragment.endLine1, fragment.startOffset1, fragment.endOffset1)
474
475     checkLineOffsets(after, fragment.startLine2, fragment.endLine2, fragment.startOffset2, fragment.endOffset2)
476   }
477
478   private fun checkLineOffsets(document: Document, startLine: Int, endLine: Int, startOffset: Int, endOffset: Int) {
479     if (startLine != endLine) {
480       assertEquals(document.getLineStartOffset(startLine), startOffset)
481       var offset = document.getLineEndOffset(endLine - 1)
482       if (offset < document.textLength) offset++
483       assertEquals(offset, endOffset)
484     }
485     else {
486       val offset = if (startLine == getLineCount(document))
487         document.textLength
488       else
489         document.getLineStartOffset(startLine)
490       assertEquals(offset, startOffset)
491       assertEquals(offset, endOffset)
492     }
493   }
494
495   private fun checkValidRanges(text1: CharSequence, text2: CharSequence, fragments: List<DiffFragment>, policy: ComparisonPolicy, skipNewline: Boolean) {
496     // TODO: better check for Trim spaces case ?
497     val ignoreSpacesUnchanged = policy != ComparisonPolicy.DEFAULT
498     val ignoreSpacesChanged = policy == ComparisonPolicy.IGNORE_WHITESPACES
499
500     val changesSet1 = BitSet()
501     val changesSet2 = BitSet()
502
503     var last1 = 0
504     var last2 = 0
505     for (fragment in fragments) {
506       val start1 = fragment.startOffset1
507       val start2 = fragment.startOffset2
508       val end1 = fragment.endOffset1
509       val end2 = fragment.endOffset2
510
511       val chunk1 = text1.subSequence(last1, start1)
512       val chunk2 = text2.subSequence(last2, start2)
513       assertEqualsCharSequences(chunk1, chunk2, ignoreSpacesUnchanged, skipNewline)
514
515       val chunkContent1 = text1.subSequence(start1, end1)
516       val chunkContent2 = text2.subSequence(start2, end2)
517       if (!skipNewline) {
518         assertNotEqualsCharSequences(chunkContent1, chunkContent2, ignoreSpacesChanged, skipNewline)
519       }
520
521       changesSet1.set(start1, end1)
522       changesSet2.set(start2, end2)
523
524       last1 = fragment.endOffset1
525       last2 = fragment.endOffset2
526     }
527     val chunk1 = text1.subSequence(last1, text1.length)
528     val chunk2 = text2.subSequence(last2, text2.length)
529     assertEqualsCharSequences(chunk1, chunk2, ignoreSpacesUnchanged, skipNewline)
530
531     checkCodePoints(text1, changesSet1)
532     checkCodePoints(text2, changesSet2)
533   }
534
535   private fun checkValidRanges3(text1: Document, text2: Document, text3: Document, fragments: List<MergeLineFragment>, policy: ComparisonPolicy) {
536     val ignoreSpaces = policy != ComparisonPolicy.DEFAULT
537
538     var last1 = 0
539     var last2 = 0
540     var last3 = 0
541     for (fragment in fragments) {
542       val start1 = fragment.startLine1
543       val start2 = fragment.startLine2
544       val start3 = fragment.startLine3
545
546       val content1 = DiffUtil.getLinesContent(text1, last1, start1)
547       val content2 = DiffUtil.getLinesContent(text2, last2, start2)
548       val content3 = DiffUtil.getLinesContent(text3, last3, start3)
549
550       assertEqualsCharSequences(content2, content1, ignoreSpaces, false)
551       assertEqualsCharSequences(content2, content3, ignoreSpaces, false)
552
553       last1 = fragment.endLine1
554       last2 = fragment.endLine2
555       last3 = fragment.endLine3
556     }
557
558     val content1 = DiffUtil.getLinesContent(text1, last1, getLineCount(text1))
559     val content2 = DiffUtil.getLinesContent(text2, last2, getLineCount(text2))
560     val content3 = DiffUtil.getLinesContent(text3, last3, getLineCount(text3))
561
562     assertEqualsCharSequences(content2, content1, ignoreSpaces, false)
563     assertEqualsCharSequences(content2, content3, ignoreSpaces, false)
564   }
565
566   private fun checkValidRanges3(text1: CharSequence, text2: CharSequence, text3: CharSequence, fragments: List<MergeWordFragment>, policy: ComparisonPolicy) {
567     val ignoreSpacesUnchanged = policy != ComparisonPolicy.DEFAULT
568     val ignoreSpacesChanged = policy == ComparisonPolicy.IGNORE_WHITESPACES
569
570     val changesSet1 = BitSet()
571     val changesSet2 = BitSet()
572     val changesSet3 = BitSet()
573
574     var last1 = 0
575     var last2 = 0
576     var last3 = 0
577     for (fragment in fragments) {
578       val start1 = fragment.startOffset1
579       val start2 = fragment.startOffset2
580       val start3 = fragment.startOffset3
581       val end1 = fragment.endOffset1
582       val end2 = fragment.endOffset2
583       val end3 = fragment.endOffset3
584
585       val content1 = text1.subSequence(last1, start1)
586       val content2 = text2.subSequence(last2, start2)
587       val content3 = text3.subSequence(last3, start3)
588       assertEqualsCharSequences(content2, content1, ignoreSpacesUnchanged, false)
589       assertEqualsCharSequences(content2, content3, ignoreSpacesUnchanged, false)
590
591       val chunkContent1 = text1.subSequence(start1, end1)
592       val chunkContent2 = text2.subSequence(start2, end2)
593       val chunkContent3 = text3.subSequence(start3, end3)
594       assertFalse(isEqualsCharSequences(chunkContent2, chunkContent1, ignoreSpacesChanged) &&
595                   isEqualsCharSequences(chunkContent2, chunkContent3, ignoreSpacesChanged))
596
597       changesSet1.set(start1, end1)
598       changesSet2.set(start2, end2)
599       changesSet3.set(start3, end3)
600
601       last1 = fragment.endOffset1
602       last2 = fragment.endOffset2
603       last3 = fragment.endOffset3
604     }
605
606     val content1 = text1.subSequence(last1, text1.length)
607     val content2 = text2.subSequence(last2, text2.length)
608     val content3 = text3.subSequence(last3, text3.length)
609
610     assertEqualsCharSequences(content2, content1, ignoreSpacesUnchanged, false)
611     assertEqualsCharSequences(content2, content3, ignoreSpacesUnchanged, false)
612
613     checkCodePoints(text1, changesSet1)
614     checkCodePoints(text2, changesSet2)
615     checkCodePoints(text3, changesSet3)
616   }
617
618   private fun checkCodePoints(text: CharSequence, changesSet: BitSet) {
619     val len = text.length
620     var offset = 0
621
622     while (offset < len) {
623       val ch = Character.codePointAt(text, offset)
624       val charCount = Character.charCount(ch)
625
626       if (charCount == 2) {
627         val state1 = changesSet[offset]
628         val state2 = changesSet[offset + 1]
629         assertEquals(state1, state2)
630       }
631
632       offset += charCount
633     }
634   }
635
636   private fun checkCantTrimLines(text1: Document, text2: Document, fragments: List<LineFragment>, policy: ComparisonPolicy, allowNonSquashed: Boolean) {
637     for (fragment in fragments) {
638       val sequence1 = getFirstLastLines(text1, fragment.startLine1, fragment.endLine1)
639       val sequence2 = getFirstLastLines(text2, fragment.startLine2, fragment.endLine2)
640       if (sequence1 == null || sequence2 == null) continue
641
642       checkNonEqualsIfLongEnough(sequence1.first, sequence2.first, policy, allowNonSquashed)
643       checkNonEqualsIfLongEnough(sequence1.second, sequence2.second, policy, allowNonSquashed)
644     }
645   }
646
647   private fun checkCantTrimLines3(text1: Document, text2: Document, text3: Document, fragments: List<MergeLineFragment>, policy: ComparisonPolicy) {
648     for (fragment in fragments) {
649       val sequence1 = getFirstLastLines(text1, fragment.startLine1, fragment.endLine1)
650       val sequence2 = getFirstLastLines(text2, fragment.startLine2, fragment.endLine2)
651       val sequence3 = getFirstLastLines(text3, fragment.startLine3, fragment.endLine3)
652       if (sequence1 == null || sequence2 == null || sequence3 == null) continue
653
654       assertFalse(MANAGER.isEquals(sequence2.first, sequence1.first, policy) && MANAGER.isEquals(sequence2.first, sequence3.first, policy))
655       assertFalse(MANAGER.isEquals(sequence2.second, sequence1.second, policy) && MANAGER.isEquals(sequence2.second, sequence3.second, policy))
656     }
657   }
658
659   private fun checkNonEqualsIfLongEnough(line1: CharSequence, line2: CharSequence, policy: ComparisonPolicy, allowNonSquashed: Boolean) {
660     // in non-squashed blocks non-trimmed elements are possible
661     if (allowNonSquashed) {
662       if (policy != ComparisonPolicy.IGNORE_WHITESPACES) return
663       if (countNonWhitespaceCharacters(line1) <= ComparisonUtil.getUnimportantLineCharCount()) return
664       if (countNonWhitespaceCharacters(line2) <= ComparisonUtil.getUnimportantLineCharCount()) return
665     }
666
667     assertFalse(MANAGER.isEquals(line1, line2, policy))
668   }
669
670   private fun countNonWhitespaceCharacters(line: CharSequence): Int {
671     return (0 until line.length).count { !StringUtil.isWhiteSpace(line[it]) }
672   }
673
674   private fun getFirstLastLines(text: Document, start: Int, end: Int): Couple<CharSequence>? {
675     if (start == end) return null
676
677     val firstLineRange = DiffUtil.getLinesRange(text, start, start + 1)
678     val lastLineRange = DiffUtil.getLinesRange(text, end - 1, end)
679
680     val firstLine = firstLineRange.subSequence(text.charsSequence)
681     val lastLine = lastLineRange.subSequence(text.charsSequence)
682
683     return Couple.of(firstLine, lastLine)
684   }
685
686   private fun Document.subSequence(start: Int, end: Int): CharSequence {
687     return this.charsSequence.subSequence(start, end)
688   }
689
690   private val MergeLineFragment.startLine1: Int get() = this.getStartLine(ThreeSide.LEFT)
691   private val MergeLineFragment.startLine2: Int get() = this.getStartLine(ThreeSide.BASE)
692   private val MergeLineFragment.startLine3: Int get() = this.getStartLine(ThreeSide.RIGHT)
693   private val MergeLineFragment.endLine1: Int get() = this.getEndLine(ThreeSide.LEFT)
694   private val MergeLineFragment.endLine2: Int get() = this.getEndLine(ThreeSide.BASE)
695   private val MergeLineFragment.endLine3: Int get() = this.getEndLine(ThreeSide.RIGHT)
696
697   private val MergeWordFragment.startOffset1: Int get() = this.getStartOffset(ThreeSide.LEFT)
698   private val MergeWordFragment.startOffset2: Int get() = this.getStartOffset(ThreeSide.BASE)
699   private val MergeWordFragment.startOffset3: Int get() = this.getStartOffset(ThreeSide.RIGHT)
700   private val MergeWordFragment.endOffset1: Int get() = this.getEndOffset(ThreeSide.LEFT)
701   private val MergeWordFragment.endOffset2: Int get() = this.getEndOffset(ThreeSide.BASE)
702   private val MergeWordFragment.endOffset3: Int get() = this.getEndOffset(ThreeSide.RIGHT)
703 }