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
 
22
 
"""test suite for weave algorithm"""
 
26
 
from bzrlib.weave import Weave, WeaveFormatError
 
27
 
from bzrlib.weavefile import write_weave, read_weave
 
28
 
from pprint import pformat
 
35
 
    from sets import Set, ImmutableSet
 
37
 
    frozenset = ImmutableSet
 
42
 
# texts for use in testing
 
43
 
TEXT_0 = ["Hello world"]
 
44
 
TEXT_1 = ["Hello world",
 
49
 
class TestBase(testsweet.TestBase):
 
50
 
    def check_read_write(self, k):
 
51
 
        """Check the weave k can be written & re-read."""
 
52
 
        from tempfile import TemporaryFile
 
61
 
            self.log('serialized weave:')
 
63
 
            self.fail('read/write check failed')
 
73
 
class StoreText(TestBase):
 
74
 
    """Store and retrieve a simple text."""
 
77
 
        idx = k.add([], TEXT_0)
 
78
 
        self.assertEqual(k.get(idx), TEXT_0)
 
79
 
        self.assertEqual(idx, 0)
 
83
 
class AnnotateOne(TestBase):
 
87
 
        self.assertEqual(k.annotate(0),
 
91
 
class StoreTwo(TestBase):
 
95
 
        idx = k.add([], TEXT_0)
 
96
 
        self.assertEqual(idx, 0)
 
98
 
        idx = k.add([], TEXT_1)
 
99
 
        self.assertEqual(idx, 1)
 
101
 
        self.assertEqual(k.get(0), TEXT_0)
 
102
 
        self.assertEqual(k.get(1), TEXT_1)
 
104
 
        k.dump(self.TEST_LOG)
 
108
 
class InvalidAdd(TestBase):
 
109
 
    """Try to use invalid version number during add."""
 
113
 
        self.assertRaises(IndexError,
 
119
 
class InsertLines(TestBase):
 
120
 
    """Store a revision that adds one line to the original.
 
122
 
    Look at the annotations to make sure that the first line is matched
 
123
 
    and not stored repeatedly."""
 
127
 
        k.add([], ['line 1'])
 
128
 
        k.add([0], ['line 1', 'line 2'])
 
130
 
        self.assertEqual(k.annotate(0),
 
133
 
        self.assertEqual(k.get(1),
 
137
 
        self.assertEqual(k.annotate(1),
 
141
 
        k.add([0], ['line 1', 'diverged line'])
 
143
 
        self.assertEqual(k.annotate(2),
 
145
 
                          (2, 'diverged line')])
 
147
 
        text3 = ['line 1', 'middle line', 'line 2']
 
151
 
        # self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
 
153
 
        self.log("k._weave=" + pformat(k._weave))
 
155
 
        self.assertEqual(k.annotate(3),
 
160
 
        # now multiple insertions at different places
 
162
 
              ['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
 
164
 
        self.assertEqual(k.annotate(4), 
 
174
 
class DeleteLines(TestBase):
 
175
 
    """Deletion of lines from existing text.
 
177
 
    Try various texts all based on a common ancestor."""
 
181
 
        base_text = ['one', 'two', 'three', 'four']
 
185
 
        texts = [['one', 'two', 'three'],
 
186
 
                 ['two', 'three', 'four'],
 
188
 
                 ['one', 'two', 'three', 'four'],
 
194
 
        self.log('final weave:')
 
195
 
        self.log('k._weave=' + pformat(k._weave))
 
197
 
        for i in range(len(texts)):
 
198
 
            self.assertEqual(k.get(i+1),
 
204
 
class SuicideDelete(TestBase):
 
205
 
    """Invalid weave which tries to add and delete simultaneously."""
 
211
 
        k._weave = [('{', 0),
 
218
 
        ################################### SKIPPED
 
219
 
        # Weave.get doesn't trap this anymore
 
222
 
        self.assertRaises(WeaveFormatError,
 
228
 
class CannedDelete(TestBase):
 
229
 
    """Unpack canned weave with deleted lines."""
 
236
 
        k._weave = [('{', 0),
 
239
 
                'line to be deleted',
 
245
 
        self.assertEqual(k.get(0),
 
247
 
                          'line to be deleted',
 
251
 
        self.assertEqual(k.get(1),
 
258
 
class CannedReplacement(TestBase):
 
259
 
    """Unpack canned weave with deleted lines."""
 
263
 
        k._parents = [frozenset(),
 
266
 
        k._weave = [('{', 0),
 
269
 
                'line to be deleted',
 
278
 
        self.assertEqual(k.get(0),
 
280
 
                          'line to be deleted',
 
284
 
        self.assertEqual(k.get(1),
 
292
 
class BadWeave(TestBase):
 
293
 
    """Test that we trap an insert which should not occur."""
 
297
 
        k._parents = [frozenset(),
 
299
 
        k._weave = ['bad line',
 
303
 
                '  added in version 1',
 
312
 
        ################################### SKIPPED
 
313
 
        # Weave.get doesn't trap this anymore
 
317
 
        self.assertRaises(WeaveFormatError,
 
322
 
class BadInsert(TestBase):
 
323
 
    """Test that we trap an insert which should not occur."""
 
327
 
        k._parents = [frozenset(),
 
332
 
        k._weave = [('{', 0),
 
335
 
                '  added in version 1',
 
343
 
        # this is not currently enforced by get
 
344
 
        return  ##########################################
 
346
 
        self.assertRaises(WeaveFormatError,
 
350
 
        self.assertRaises(WeaveFormatError,
 
355
 
class InsertNested(TestBase):
 
356
 
    """Insertion with nested instructions."""
 
360
 
        k._parents = [frozenset(),
 
365
 
        k._weave = [('{', 0),
 
368
 
                '  added in version 1',
 
377
 
        self.assertEqual(k.get(0),
 
381
 
        self.assertEqual(k.get(1),
 
383
 
                          '  added in version 1',
 
387
 
        self.assertEqual(k.get(2),
 
392
 
        self.assertEqual(k.get(3),
 
394
 
                          '  added in version 1',
 
401
 
class DeleteLines2(TestBase):
 
402
 
    """Test recording revisions that delete lines.
 
404
 
    This relies on the weave having a way to represent lines knocked
 
405
 
    out by a later revision."""
 
409
 
        k.add([], ["line the first",
 
414
 
        self.assertEqual(len(k.get(0)), 4)
 
416
 
        k.add([0], ["line the first",
 
419
 
        self.assertEqual(k.get(1),
 
423
 
        self.assertEqual(k.annotate(1),
 
424
 
                         [(0, "line the first"),
 
429
 
class IncludeVersions(TestBase):
 
430
 
    """Check texts that are stored across multiple revisions.
 
432
 
    Here we manually create a weave with particular encoding and make
 
433
 
    sure it unpacks properly.
 
435
 
    Text 0 includes nothing; text 1 includes text 0 and adds some
 
442
 
        k._parents = [frozenset(), frozenset([0])]
 
443
 
        k._weave = [('{', 0),
 
450
 
        self.assertEqual(k.get(1),
 
454
 
        self.assertEqual(k.get(0),
 
457
 
        k.dump(self.TEST_LOG)
 
460
 
class DivergedIncludes(TestBase):
 
461
 
    """Weave with two diverged texts based on version 0.
 
466
 
        k._parents = [frozenset(),
 
470
 
        k._weave = [('{', 0),
 
477
 
                "alternative second line",
 
481
 
        self.assertEqual(k.get(0),
 
484
 
        self.assertEqual(k.get(1),
 
488
 
        self.assertEqual(k.get(2),
 
490
 
                          "alternative second line"])
 
492
 
        self.assertEqual(list(k.inclusions([2])),
 
497
 
class ReplaceLine(TestBase):
 
501
 
        text0 = ['cheddar', 'stilton', 'gruyere']
 
502
 
        text1 = ['cheddar', 'blue vein', 'neufchatel', 'chevre']
 
507
 
        self.log('k._weave=' + pformat(k._weave))
 
509
 
        self.assertEqual(k.get(0), text0)
 
510
 
        self.assertEqual(k.get(1), text1)
 
514
 
class Merge(TestBase):
 
515
 
    """Storage of versions that merge diverged parents"""
 
520
 
                 ['header', '', 'line from 1'],
 
521
 
                 ['header', '', 'line from 2', 'more from 2'],
 
522
 
                 ['header', '', 'line from 1', 'fixup line', 'line from 2'],
 
528
 
        k.add([0, 1, 2], texts[3])
 
530
 
        for i, t in enumerate(texts):
 
531
 
            self.assertEqual(k.get(i), t)
 
533
 
        self.assertEqual(k.annotate(3),
 
541
 
        self.assertEqual(list(k.inclusions([3])),
 
544
 
        self.log('k._weave=' + pformat(k._weave))
 
546
 
        self.check_read_write(k)
 
549
 
class Conflicts(TestBase):
 
550
 
    """Test detection of conflicting regions during a merge.
 
552
 
    A base version is inserted, then two descendents try to
 
553
 
    insert different lines in the same place.  These should be
 
554
 
    reported as a possible conflict and forwarded to the user."""
 
559
 
        k.add([], ['aaa', 'bbb'])
 
560
 
        k.add([0], ['aaa', '111', 'bbb'])
 
561
 
        k.add([1], ['aaa', '222', 'bbb'])
 
563
 
        merged = k.merge([1, 2])
 
565
 
        self.assertEquals([[['aaa']],
 
571
 
class NonConflict(TestBase):
 
572
 
    """Two descendants insert compatible changes.
 
574
 
    No conflict should be reported."""
 
579
 
        k.add([], ['aaa', 'bbb'])
 
580
 
        k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
 
581
 
        k.add([1], ['aaa', 'ccc', 'bbb', '222'])
 
587
 
class AutoMerge(TestBase):
 
591
 
        texts = [['header', 'aaa', 'bbb'],
 
592
 
                 ['header', 'aaa', 'line from 1', 'bbb'],
 
593
 
                 ['header', 'aaa', 'bbb', 'line from 2', 'more from 2'],
 
600
 
        self.log('k._weave=' + pformat(k._weave))
 
602
 
        m = list(k.mash_iter([0, 1, 2]))
 
608
 
                          'line from 2', 'more from 2'])
 
612
 
class Khayyam(TestBase):
 
613
 
    """Test changes to multi-line texts, and read/write"""
 
616
 
            """A Book of Verses underneath the Bough,
 
617
 
            A Jug of Wine, a Loaf of Bread, -- and Thou
 
618
 
            Beside me singing in the Wilderness --
 
619
 
            Oh, Wilderness were Paradise enow!""",
 
621
 
            """A Book of Verses underneath the Bough,
 
622
 
            A Jug of Wine, a Loaf of Bread, -- and Thou
 
623
 
            Beside me singing in the Wilderness --
 
624
 
            Oh, Wilderness were Paradise now!""",
 
626
 
            """A Book of poems underneath the tree,
 
627
 
            A Jug of Wine, a Loaf of Bread,
 
629
 
            Beside me singing in the Wilderness --
 
630
 
            Oh, Wilderness were Paradise now!
 
634
 
            """A Book of Verses underneath the Bough,
 
635
 
            A Jug of Wine, a Loaf of Bread,
 
637
 
            Beside me singing in the Wilderness --
 
638
 
            Oh, Wilderness were Paradise now!""",
 
640
 
        texts = [[l.strip() for l in t.split('\n')] for t in rawtexts]
 
645
 
            ver = k.add(list(parents), t)
 
648
 
        self.log("k._weave=" + pformat(k._weave))
 
650
 
        for i, t in enumerate(texts):
 
651
 
            self.assertEqual(k.get(i), t)
 
653
 
        self.check_read_write(k)
 
657
 
class MergeCases(TestBase):
 
658
 
    def doMerge(self, base, a, b, mp):
 
659
 
        from cStringIO import StringIO
 
660
 
        from textwrap import dedent
 
666
 
        w.add([], map(addcrlf, base))
 
667
 
        w.add([0], map(addcrlf, a))
 
668
 
        w.add([0], map(addcrlf, b))
 
670
 
        self.log('weave is:')
 
673
 
        self.log(tmpf.getvalue())
 
675
 
        self.log('merge plan:')
 
676
 
        p = list(w.plan_merge(1, 2))
 
677
 
        for state, line in p:
 
679
 
                self.log('%12s | %s' % (state, line[:-1]))
 
683
 
        mt.writelines(w.weave_merge(p))
 
685
 
        self.log(mt.getvalue())
 
687
 
        mp = map(addcrlf, mp)
 
688
 
        self.assertEqual(mt.readlines(), mp)
 
691
 
    def testOneInsert(self):
 
697
 
    def testSeparateInserts(self):
 
698
 
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
699
 
                     ['aaa', 'xxx', 'bbb', 'ccc'],
 
700
 
                     ['aaa', 'bbb', 'yyy', 'ccc'],
 
701
 
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
 
703
 
    def testSameInsert(self):
 
704
 
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
705
 
                     ['aaa', 'xxx', 'bbb', 'ccc'],
 
706
 
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
 
707
 
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
 
709
 
    def testOverlappedInsert(self):
 
710
 
        self.doMerge(['aaa', 'bbb'],
 
711
 
                     ['aaa', 'xxx', 'yyy', 'bbb'],
 
712
 
                     ['aaa', 'xxx', 'bbb'],
 
713
 
                     ['aaa', '<<<<', 'xxx', 'yyy', '====', 'xxx', '>>>>', 'bbb'])
 
715
 
        # really it ought to reduce this to 
 
716
 
        # ['aaa', 'xxx', 'yyy', 'bbb']
 
719
 
    def testClashReplace(self):
 
720
 
        self.doMerge(['aaa'],
 
723
 
                     ['<<<<', 'xxx', '====', 'yyy', 'zzz', '>>>>'])
 
725
 
    def testNonClashInsert(self):
 
726
 
        self.doMerge(['aaa'],
 
729
 
                     ['<<<<', 'xxx', 'aaa', '====', 'yyy', 'zzz', '>>>>'])
 
731
 
        self.doMerge(['aaa'],
 
737
 
    def testDeleteAndModify(self):
 
738
 
        """Clashing delete and modification.
 
740
 
        If one side modifies a region and the other deletes it then
 
741
 
        there should be a conflict with one side blank.
 
744
 
        #######################################
 
745
 
        # skippd, not working yet
 
748
 
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
749
 
                     ['aaa', 'ddd', 'ccc'],
 
751
 
                     ['<<<<', 'aaa', '====', '>>>>', 'ccc'])
 
757
 
    from unittest import TestSuite, TestLoader
 
762
 
    suite.addTest(tl.loadTestsFromModule(testweave))
 
764
 
    return int(not testsweet.run_suite(suite)) # for shell 0=true
 
767
 
if __name__ == '__main__':
 
769
 
    sys.exit(testweave())