3
# Copyright (C) 2005 by Canonical Ltd
 
 
5
# This program is free software; you can redistribute it and/or modify
 
 
6
# it under the terms of the GNU General Public License as published by
 
 
7
# the Free Software Foundation; either version 2 of the License, or
 
 
8
# (at your option) any later version.
 
 
10
# This program is distributed in the hope that it will be useful,
 
 
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 
13
# GNU General Public License for more details.
 
 
15
# You should have received a copy of the GNU General Public License
 
 
16
# along with this program; if not, write to the Free Software
 
 
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
20
# TODO: tests regarding version names
 
 
24
"""test suite for weave algorithm"""
 
 
28
from bzrlib.weave import Weave, WeaveFormatError
 
 
29
from bzrlib.weavefile import write_weave, read_weave
 
 
30
from pprint import pformat
 
 
37
    from sets import Set, ImmutableSet
 
 
39
    frozenset = ImmutableSet
 
 
44
# texts for use in testing
 
 
45
TEXT_0 = ["Hello world"]
 
 
46
TEXT_1 = ["Hello world",
 
 
51
class TestBase(testsweet.TestBase):
 
 
52
    def check_read_write(self, k):
 
 
53
        """Check the weave k can be written & re-read."""
 
 
54
        from tempfile import TemporaryFile
 
 
63
            self.log('serialized weave:')
 
 
67
            self.log('parents: %s' % (k._parents == k2._parents))
 
 
68
            self.log('         %r' % k._parents)
 
 
69
            self.log('         %r' % k2._parents)
 
 
73
            self.fail('read/write check failed')
 
 
83
class StoreText(TestBase):
 
 
84
    """Store and retrieve a simple text."""
 
 
87
        idx = k.add('text0', [], TEXT_0)
 
 
88
        self.assertEqual(k.get(idx), TEXT_0)
 
 
89
        self.assertEqual(idx, 0)
 
 
93
class AnnotateOne(TestBase):
 
 
96
        k.add('text0', [], TEXT_0)
 
 
97
        self.assertEqual(k.annotate(0),
 
 
101
class StoreTwo(TestBase):
 
 
105
        idx = k.add('text0', [], TEXT_0)
 
 
106
        self.assertEqual(idx, 0)
 
 
108
        idx = k.add('text1', [], TEXT_1)
 
 
109
        self.assertEqual(idx, 1)
 
 
111
        self.assertEqual(k.get(0), TEXT_0)
 
 
112
        self.assertEqual(k.get(1), TEXT_1)
 
 
114
        k.dump(self.TEST_LOG)
 
 
118
class InvalidAdd(TestBase):
 
 
119
    """Try to use invalid version number during add."""
 
 
123
        self.assertRaises(IndexError,
 
 
130
class InsertLines(TestBase):
 
 
131
    """Store a revision that adds one line to the original.
 
 
133
    Look at the annotations to make sure that the first line is matched
 
 
134
    and not stored repeatedly."""
 
 
138
        k.add('text0', [], ['line 1'])
 
 
139
        k.add('text1', [0], ['line 1', 'line 2'])
 
 
141
        self.assertEqual(k.annotate(0),
 
 
144
        self.assertEqual(k.get(1),
 
 
148
        self.assertEqual(k.annotate(1),
 
 
152
        k.add('text2', [0], ['line 1', 'diverged line'])
 
 
154
        self.assertEqual(k.annotate(2),
 
 
156
                          (2, 'diverged line')])
 
 
158
        text3 = ['line 1', 'middle line', 'line 2']
 
 
163
        # self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
 
 
165
        self.log("k._weave=" + pformat(k._weave))
 
 
167
        self.assertEqual(k.annotate(3),
 
 
172
        # now multiple insertions at different places
 
 
175
              ['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
 
 
177
        self.assertEqual(k.annotate(4), 
 
 
187
class DeleteLines(TestBase):
 
 
188
    """Deletion of lines from existing text.
 
 
190
    Try various texts all based on a common ancestor."""
 
 
194
        base_text = ['one', 'two', 'three', 'four']
 
 
196
        k.add('text0', [], base_text)
 
 
198
        texts = [['one', 'two', 'three'],
 
 
199
                 ['two', 'three', 'four'],
 
 
201
                 ['one', 'two', 'three', 'four'],
 
 
206
            ver = k.add('text%d' % i,
 
 
210
        self.log('final weave:')
 
 
211
        self.log('k._weave=' + pformat(k._weave))
 
 
213
        for i in range(len(texts)):
 
 
214
            self.assertEqual(k.get(i+1),
 
 
220
class SuicideDelete(TestBase):
 
 
221
    """Invalid weave which tries to add and delete simultaneously."""
 
 
227
        k._weave = [('{', 0),
 
 
234
        ################################### SKIPPED
 
 
235
        # Weave.get doesn't trap this anymore
 
 
238
        self.assertRaises(WeaveFormatError,
 
 
244
class CannedDelete(TestBase):
 
 
245
    """Unpack canned weave with deleted lines."""
 
 
252
        k._weave = [('{', 0),
 
 
255
                'line to be deleted',
 
 
261
        self.assertEqual(k.get(0),
 
 
263
                          'line to be deleted',
 
 
267
        self.assertEqual(k.get(1),
 
 
274
class CannedReplacement(TestBase):
 
 
275
    """Unpack canned weave with deleted lines."""
 
 
279
        k._parents = [frozenset(),
 
 
282
        k._weave = [('{', 0),
 
 
285
                'line to be deleted',
 
 
294
        self.assertEqual(k.get(0),
 
 
296
                          'line to be deleted',
 
 
300
        self.assertEqual(k.get(1),
 
 
308
class BadWeave(TestBase):
 
 
309
    """Test that we trap an insert which should not occur."""
 
 
313
        k._parents = [frozenset(),
 
 
315
        k._weave = ['bad line',
 
 
319
                '  added in version 1',
 
 
328
        ################################### SKIPPED
 
 
329
        # Weave.get doesn't trap this anymore
 
 
333
        self.assertRaises(WeaveFormatError,
 
 
338
class BadInsert(TestBase):
 
 
339
    """Test that we trap an insert which should not occur."""
 
 
343
        k._parents = [frozenset(),
 
 
348
        k._weave = [('{', 0),
 
 
351
                '  added in version 1',
 
 
359
        # this is not currently enforced by get
 
 
360
        return  ##########################################
 
 
362
        self.assertRaises(WeaveFormatError,
 
 
366
        self.assertRaises(WeaveFormatError,
 
 
371
class InsertNested(TestBase):
 
 
372
    """Insertion with nested instructions."""
 
 
376
        k._parents = [frozenset(),
 
 
381
        k._weave = [('{', 0),
 
 
384
                '  added in version 1',
 
 
393
        self.assertEqual(k.get(0),
 
 
397
        self.assertEqual(k.get(1),
 
 
399
                          '  added in version 1',
 
 
403
        self.assertEqual(k.get(2),
 
 
408
        self.assertEqual(k.get(3),
 
 
410
                          '  added in version 1',
 
 
417
class DeleteLines2(TestBase):
 
 
418
    """Test recording revisions that delete lines.
 
 
420
    This relies on the weave having a way to represent lines knocked
 
 
421
    out by a later revision."""
 
 
425
        k.add('text0', [], ["line the first",
 
 
430
        self.assertEqual(len(k.get(0)), 4)
 
 
432
        k.add('text1', [0], ["line the first",
 
 
435
        self.assertEqual(k.get(1),
 
 
439
        self.assertEqual(k.annotate(1),
 
 
440
                         [(0, "line the first"),
 
 
445
class IncludeVersions(TestBase):
 
 
446
    """Check texts that are stored across multiple revisions.
 
 
448
    Here we manually create a weave with particular encoding and make
 
 
449
    sure it unpacks properly.
 
 
451
    Text 0 includes nothing; text 1 includes text 0 and adds some
 
 
458
        k._parents = [frozenset(), frozenset([0])]
 
 
459
        k._weave = [('{', 0),
 
 
466
        self.assertEqual(k.get(1),
 
 
470
        self.assertEqual(k.get(0),
 
 
473
        k.dump(self.TEST_LOG)
 
 
476
class DivergedIncludes(TestBase):
 
 
477
    """Weave with two diverged texts based on version 0.
 
 
482
        k._parents = [frozenset(),
 
 
486
        k._weave = [('{', 0),
 
 
493
                "alternative second line",
 
 
497
        self.assertEqual(k.get(0),
 
 
500
        self.assertEqual(k.get(1),
 
 
504
        self.assertEqual(k.get(2),
 
 
506
                          "alternative second line"])
 
 
508
        self.assertEqual(list(k.inclusions([2])),
 
 
513
class ReplaceLine(TestBase):
 
 
517
        text0 = ['cheddar', 'stilton', 'gruyere']
 
 
518
        text1 = ['cheddar', 'blue vein', 'neufchatel', 'chevre']
 
 
520
        k.add('text0', [], text0)
 
 
521
        k.add('text1', [0], text1)
 
 
523
        self.log('k._weave=' + pformat(k._weave))
 
 
525
        self.assertEqual(k.get(0), text0)
 
 
526
        self.assertEqual(k.get(1), text1)
 
 
530
class Merge(TestBase):
 
 
531
    """Storage of versions that merge diverged parents"""
 
 
536
                 ['header', '', 'line from 1'],
 
 
537
                 ['header', '', 'line from 2', 'more from 2'],
 
 
538
                 ['header', '', 'line from 1', 'fixup line', 'line from 2'],
 
 
541
        k.add('text0', [], texts[0])
 
 
542
        k.add('text1', [0], texts[1])
 
 
543
        k.add('text2', [0], texts[2])
 
 
544
        k.add('merge', [0, 1, 2], texts[3])
 
 
546
        for i, t in enumerate(texts):
 
 
547
            self.assertEqual(k.get(i), t)
 
 
549
        self.assertEqual(k.annotate(3),
 
 
557
        self.assertEqual(list(k.inclusions([3])),
 
 
560
        self.log('k._weave=' + pformat(k._weave))
 
 
562
        self.check_read_write(k)
 
 
565
class Conflicts(TestBase):
 
 
566
    """Test detection of conflicting regions during a merge.
 
 
568
    A base version is inserted, then two descendents try to
 
 
569
    insert different lines in the same place.  These should be
 
 
570
    reported as a possible conflict and forwarded to the user."""
 
 
575
        k.add([], ['aaa', 'bbb'])
 
 
576
        k.add([0], ['aaa', '111', 'bbb'])
 
 
577
        k.add([1], ['aaa', '222', 'bbb'])
 
 
579
        merged = k.merge([1, 2])
 
 
581
        self.assertEquals([[['aaa']],
 
 
587
class NonConflict(TestBase):
 
 
588
    """Two descendants insert compatible changes.
 
 
590
    No conflict should be reported."""
 
 
595
        k.add([], ['aaa', 'bbb'])
 
 
596
        k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
 
 
597
        k.add([1], ['aaa', 'ccc', 'bbb', '222'])
 
 
603
class AutoMerge(TestBase):
 
 
607
        texts = [['header', 'aaa', 'bbb'],
 
 
608
                 ['header', 'aaa', 'line from 1', 'bbb'],
 
 
609
                 ['header', 'aaa', 'bbb', 'line from 2', 'more from 2'],
 
 
612
        k.add('text0', [], texts[0])
 
 
613
        k.add('text1', [0], texts[1])
 
 
614
        k.add('text2', [0], texts[2])
 
 
616
        self.log('k._weave=' + pformat(k._weave))
 
 
618
        m = list(k.mash_iter([0, 1, 2]))
 
 
624
                          'line from 2', 'more from 2'])
 
 
628
class Khayyam(TestBase):
 
 
629
    """Test changes to multi-line texts, and read/write"""
 
 
632
            """A Book of Verses underneath the Bough,
 
 
633
            A Jug of Wine, a Loaf of Bread, -- and Thou
 
 
634
            Beside me singing in the Wilderness --
 
 
635
            Oh, Wilderness were Paradise enow!""",
 
 
637
            """A Book of Verses underneath the Bough,
 
 
638
            A Jug of Wine, a Loaf of Bread, -- and Thou
 
 
639
            Beside me singing in the Wilderness --
 
 
640
            Oh, Wilderness were Paradise now!""",
 
 
642
            """A Book of poems underneath the tree,
 
 
643
            A Jug of Wine, a Loaf of Bread,
 
 
645
            Beside me singing in the Wilderness --
 
 
646
            Oh, Wilderness were Paradise now!
 
 
650
            """A Book of Verses underneath the Bough,
 
 
651
            A Jug of Wine, a Loaf of Bread,
 
 
653
            Beside me singing in the Wilderness --
 
 
654
            Oh, Wilderness were Paradise now!""",
 
 
656
        texts = [[l.strip() for l in t.split('\n')] for t in rawtexts]
 
 
662
            ver = k.add('text%d' % i,
 
 
667
        self.log("k._weave=" + pformat(k._weave))
 
 
669
        for i, t in enumerate(texts):
 
 
670
            self.assertEqual(k.get(i), t)
 
 
672
        self.check_read_write(k)
 
 
676
class MergeCases(TestBase):
 
 
677
    def doMerge(self, base, a, b, mp):
 
 
678
        from cStringIO import StringIO
 
 
679
        from textwrap import dedent
 
 
685
        w.add('text0', [], map(addcrlf, base))
 
 
686
        w.add('text1', [0], map(addcrlf, a))
 
 
687
        w.add('text2', [0], map(addcrlf, b))
 
 
689
        self.log('weave is:')
 
 
692
        self.log(tmpf.getvalue())
 
 
694
        self.log('merge plan:')
 
 
695
        p = list(w.plan_merge(1, 2))
 
 
696
        for state, line in p:
 
 
698
                self.log('%12s | %s' % (state, line[:-1]))
 
 
702
        mt.writelines(w.weave_merge(p))
 
 
704
        self.log(mt.getvalue())
 
 
706
        mp = map(addcrlf, mp)
 
 
707
        self.assertEqual(mt.readlines(), mp)
 
 
710
    def testOneInsert(self):
 
 
716
    def testSeparateInserts(self):
 
 
717
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
 
718
                     ['aaa', 'xxx', 'bbb', 'ccc'],
 
 
719
                     ['aaa', 'bbb', 'yyy', 'ccc'],
 
 
720
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
 
 
722
    def testSameInsert(self):
 
 
723
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
 
724
                     ['aaa', 'xxx', 'bbb', 'ccc'],
 
 
725
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
 
 
726
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
 
 
728
    def testOverlappedInsert(self):
 
 
729
        self.doMerge(['aaa', 'bbb'],
 
 
730
                     ['aaa', 'xxx', 'yyy', 'bbb'],
 
 
731
                     ['aaa', 'xxx', 'bbb'],
 
 
732
                     ['aaa', '<<<<', 'xxx', 'yyy', '====', 'xxx', '>>>>', 'bbb'])
 
 
734
        # really it ought to reduce this to 
 
 
735
        # ['aaa', 'xxx', 'yyy', 'bbb']
 
 
738
    def testClashReplace(self):
 
 
739
        self.doMerge(['aaa'],
 
 
742
                     ['<<<<', 'xxx', '====', 'yyy', 'zzz', '>>>>'])
 
 
744
    def testNonClashInsert(self):
 
 
745
        self.doMerge(['aaa'],
 
 
748
                     ['<<<<', 'xxx', 'aaa', '====', 'yyy', 'zzz', '>>>>'])
 
 
750
        self.doMerge(['aaa'],
 
 
756
    def testDeleteAndModify(self):
 
 
757
        """Clashing delete and modification.
 
 
759
        If one side modifies a region and the other deletes it then
 
 
760
        there should be a conflict with one side blank.
 
 
763
        #######################################
 
 
764
        # skippd, not working yet
 
 
767
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
 
768
                     ['aaa', 'ddd', 'ccc'],
 
 
770
                     ['<<<<', 'aaa', '====', '>>>>', 'ccc'])
 
 
776
    from unittest import TestSuite, TestLoader
 
 
781
    suite.addTest(tl.loadTestsFromModule(testweave))
 
 
783
    return int(not testsweet.run_suite(suite)) # for shell 0=true
 
 
786
if __name__ == '__main__':
 
 
788
    sys.exit(testweave())