~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to tools/testweave.py

  • Committer: Martin Pool
  • Date: 2005-07-07 10:22:02 UTC
  • Revision ID: mbp@sourcefrog.net-20050707102201-2d2a13a25098b101
- rearrange and clear up merged weave

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/python2.4
 
2
 
 
3
# Copyright (C) 2005 by Canonical Ltd
 
4
 
 
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.
 
9
 
 
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.
 
14
 
 
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
 
18
 
 
19
 
 
20
 
 
21
 
 
22
"""test suite for weave algorithm"""
 
23
 
 
24
 
 
25
import testsweet
 
26
from weave import Weave, WeaveFormatError
 
27
from pprint import pformat
 
28
 
 
29
 
 
30
 
 
31
try:
 
32
    set
 
33
    frozenset
 
34
except NameError:
 
35
    from sets import Set, ImmutableSet
 
36
    set = Set
 
37
    frozenset = ImmutableSet
 
38
    del Set, ImmutableSet
 
39
 
 
40
 
 
41
 
 
42
# texts for use in testing
 
43
TEXT_0 = ["Hello world"]
 
44
TEXT_1 = ["Hello world",
 
45
          "A second line"]
 
46
 
 
47
 
 
48
 
 
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
 
53
        from weavefile import write_weave, read_weave
 
54
        tf = TemporaryFile()
 
55
 
 
56
        write_weave(k, tf)
 
57
        tf.seek(0)
 
58
        k2 = read_weave(tf)
 
59
 
 
60
        if k != k2:
 
61
            tf.seek(0)
 
62
            self.log('serialized weave:')
 
63
            self.log(tf.read())
 
64
            self.fail('read/write check failed')
 
65
        
 
66
        
 
67
 
 
68
 
 
69
class Easy(TestBase):
 
70
    def runTest(self):
 
71
        k = Weave()
 
72
 
 
73
 
 
74
class StoreText(TestBase):
 
75
    """Store and retrieve a simple text."""
 
76
    def runTest(self):
 
77
        k = Weave()
 
78
        idx = k.add([], TEXT_0)
 
79
        self.assertEqual(k.get(idx), TEXT_0)
 
80
        self.assertEqual(idx, 0)
 
81
 
 
82
 
 
83
 
 
84
class AnnotateOne(TestBase):
 
85
    def runTest(self):
 
86
        k = Weave()
 
87
        k.add([], TEXT_0)
 
88
        self.assertEqual(k.annotate(0),
 
89
                         [(0, TEXT_0[0])])
 
90
 
 
91
 
 
92
class StoreTwo(TestBase):
 
93
    def runTest(self):
 
94
        k = Weave()
 
95
 
 
96
        idx = k.add([], TEXT_0)
 
97
        self.assertEqual(idx, 0)
 
98
 
 
99
        idx = k.add([], TEXT_1)
 
100
        self.assertEqual(idx, 1)
 
101
 
 
102
        self.assertEqual(k.get(0), TEXT_0)
 
103
        self.assertEqual(k.get(1), TEXT_1)
 
104
 
 
105
        k.dump(self.TEST_LOG)
 
106
 
 
107
 
 
108
 
 
109
class DeltaAdd(TestBase):
 
110
    """Detection of changes prior to inserting new revision."""
 
111
    def runTest(self):
 
112
        k = Weave()
 
113
        k.add([], ['line 1'])
 
114
 
 
115
        self.assertEqual(k._l,
 
116
                         [('{', 0),
 
117
                          'line 1',
 
118
                          ('}', 0),
 
119
                          ])
 
120
 
 
121
        changes = list(k._delta(set([0]),
 
122
                                ['line 1',
 
123
                                 'new line']))
 
124
 
 
125
        self.log('raw changes: ' + pformat(changes))
 
126
 
 
127
        # currently there are 3 lines in the weave, and we insert after them
 
128
        self.assertEquals(changes,
 
129
                          [(3, 3, ['new line'])])
 
130
 
 
131
        changes = k._delta(set([0]),
 
132
                           ['top line',
 
133
                            'line 1'])
 
134
        
 
135
        self.assertEquals(list(changes),
 
136
                          [(1, 1, ['top line'])])
 
137
 
 
138
        self.check_read_write(k)
 
139
 
 
140
 
 
141
class InvalidAdd(TestBase):
 
142
    """Try to use invalid version number during add."""
 
143
    def runTest(self):
 
144
        k = Weave()
 
145
 
 
146
        self.assertRaises(IndexError,
 
147
                          k.add,
 
148
                          [69],
 
149
                          ['new text!'])
 
150
 
 
151
 
 
152
class InsertLines(TestBase):
 
153
    """Store a revision that adds one line to the original.
 
154
 
 
155
    Look at the annotations to make sure that the first line is matched
 
156
    and not stored repeatedly."""
 
157
    def runTest(self):
 
158
        k = Weave()
 
159
 
 
160
        k.add([], ['line 1'])
 
161
        k.add([0], ['line 1', 'line 2'])
 
162
 
 
163
        self.assertEqual(k.annotate(0),
 
164
                         [(0, 'line 1')])
 
165
 
 
166
        self.assertEqual(k.get(1),
 
167
                         ['line 1',
 
168
                          'line 2'])
 
169
 
 
170
        self.assertEqual(k.annotate(1),
 
171
                         [(0, 'line 1'),
 
172
                          (1, 'line 2')])
 
173
 
 
174
        k.add([0], ['line 1', 'diverged line'])
 
175
 
 
176
        self.assertEqual(k.annotate(2),
 
177
                         [(0, 'line 1'),
 
178
                          (2, 'diverged line')])
 
179
 
 
180
        text3 = ['line 1', 'middle line', 'line 2']
 
181
        k.add([0, 1],
 
182
              text3)
 
183
 
 
184
        self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
 
185
 
 
186
        self.log("k._l=" + pformat(k._l))
 
187
 
 
188
        self.assertEqual(k.annotate(3),
 
189
                         [(0, 'line 1'),
 
190
                          (3, 'middle line'),
 
191
                          (1, 'line 2')])
 
192
 
 
193
        # now multiple insertions at different places
 
194
        k.add([0, 1, 3],
 
195
              ['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
 
196
 
 
197
        self.assertEqual(k.annotate(4), 
 
198
                         [(0, 'line 1'),
 
199
                          (4, 'aaa'),
 
200
                          (3, 'middle line'),
 
201
                          (4, 'bbb'),
 
202
                          (1, 'line 2'),
 
203
                          (4, 'ccc')])
 
204
 
 
205
 
 
206
 
 
207
class DeleteLines(TestBase):
 
208
    """Deletion of lines from existing text.
 
209
 
 
210
    Try various texts all based on a common ancestor."""
 
211
    def runTest(self):
 
212
        k = Weave()
 
213
 
 
214
        base_text = ['one', 'two', 'three', 'four']
 
215
 
 
216
        k.add([], base_text)
 
217
        
 
218
        texts = [['one', 'two', 'three'],
 
219
                 ['two', 'three', 'four'],
 
220
                 ['one', 'four'],
 
221
                 ['one', 'two', 'three', 'four'],
 
222
                 ]
 
223
 
 
224
        for t in texts:
 
225
            ver = k.add([0], t)
 
226
 
 
227
        self.log('final weave:')
 
228
        self.log('k._l=' + pformat(k._l))
 
229
 
 
230
        for i in range(len(texts)):
 
231
            self.assertEqual(k.get(i+1),
 
232
                             texts[i])
 
233
            
 
234
 
 
235
 
 
236
 
 
237
class SuicideDelete(TestBase):
 
238
    """Invalid weave which tries to add and delete simultaneously."""
 
239
    def runTest(self):
 
240
        k = Weave()
 
241
 
 
242
        k._v = [(),
 
243
                ]
 
244
        k._l = [('{', 0),
 
245
                'first line',
 
246
                ('[', 0),
 
247
                'deleted in 0',
 
248
                (']', 0),
 
249
                ('}', 0),
 
250
                ]
 
251
 
 
252
        self.assertRaises(WeaveFormatError,
 
253
                          k.get,
 
254
                          0)        
 
255
 
 
256
 
 
257
 
 
258
class CannedDelete(TestBase):
 
259
    """Unpack canned weave with deleted lines."""
 
260
    def runTest(self):
 
261
        k = Weave()
 
262
 
 
263
        k._v = [(),
 
264
                frozenset([0]),
 
265
                ]
 
266
        k._l = [('{', 0),
 
267
                'first line',
 
268
                ('[', 1),
 
269
                'line to be deleted',
 
270
                (']', 1),
 
271
                'last line',
 
272
                ('}', 0),
 
273
                ]
 
274
 
 
275
        self.assertEqual(k.get(0),
 
276
                         ['first line',
 
277
                          'line to be deleted',
 
278
                          'last line',
 
279
                          ])
 
280
 
 
281
        self.assertEqual(k.get(1),
 
282
                         ['first line',
 
283
                          'last line',
 
284
                          ])
 
285
 
 
286
 
 
287
 
 
288
class CannedReplacement(TestBase):
 
289
    """Unpack canned weave with deleted lines."""
 
290
    def runTest(self):
 
291
        k = Weave()
 
292
 
 
293
        k._v = [frozenset(),
 
294
                frozenset([0]),
 
295
                ]
 
296
        k._l = [('{', 0),
 
297
                'first line',
 
298
                ('[', 1),
 
299
                'line to be deleted',
 
300
                (']', 1),
 
301
                ('{', 1),
 
302
                'replacement line',                
 
303
                ('}', 1),
 
304
                'last line',
 
305
                ('}', 0),
 
306
                ]
 
307
 
 
308
        self.assertEqual(k.get(0),
 
309
                         ['first line',
 
310
                          'line to be deleted',
 
311
                          'last line',
 
312
                          ])
 
313
 
 
314
        self.assertEqual(k.get(1),
 
315
                         ['first line',
 
316
                          'replacement line',
 
317
                          'last line',
 
318
                          ])
 
319
 
 
320
 
 
321
 
 
322
class BadWeave(TestBase):
 
323
    """Test that we trap an insert which should not occur."""
 
324
    def runTest(self):
 
325
        k = Weave()
 
326
 
 
327
        k._v = [frozenset(),
 
328
                ]
 
329
        k._l = ['bad line',
 
330
                ('{', 0),
 
331
                'foo {',
 
332
                ('{', 1),
 
333
                '  added in version 1',
 
334
                ('{', 2),
 
335
                '  added in v2',
 
336
                ('}', 2),
 
337
                '  also from v1',
 
338
                ('}', 1),
 
339
                '}',
 
340
                ('}', 0)]
 
341
 
 
342
        self.assertRaises(WeaveFormatError,
 
343
                          k.get,
 
344
                          0)
 
345
 
 
346
 
 
347
class BadInsert(TestBase):
 
348
    """Test that we trap an insert which should not occur."""
 
349
    def runTest(self):
 
350
        k = Weave()
 
351
 
 
352
        k._v = [frozenset(),
 
353
                frozenset([0]),
 
354
                frozenset([0]),
 
355
                frozenset([0,1,2]),
 
356
                ]
 
357
        k._l = [('{', 0),
 
358
                'foo {',
 
359
                ('{', 1),
 
360
                '  added in version 1',
 
361
                ('{', 1),
 
362
                '  more in 1',
 
363
                ('}', 1),
 
364
                ('}', 1),
 
365
                ('}', 0)]
 
366
 
 
367
        self.assertRaises(WeaveFormatError,
 
368
                          k.get,
 
369
                          0)
 
370
 
 
371
        self.assertRaises(WeaveFormatError,
 
372
                          k.get,
 
373
                          1)
 
374
 
 
375
 
 
376
class InsertNested(TestBase):
 
377
    """Insertion with nested instructions."""
 
378
    def runTest(self):
 
379
        k = Weave()
 
380
 
 
381
        k._v = [frozenset(),
 
382
                frozenset([0]),
 
383
                frozenset([0]),
 
384
                frozenset([0,1,2]),
 
385
                ]
 
386
        k._l = [('{', 0),
 
387
                'foo {',
 
388
                ('{', 1),
 
389
                '  added in version 1',
 
390
                ('{', 2),
 
391
                '  added in v2',
 
392
                ('}', 2),
 
393
                '  also from v1',
 
394
                ('}', 1),
 
395
                '}',
 
396
                ('}', 0)]
 
397
 
 
398
        self.assertEqual(k.get(0),
 
399
                         ['foo {',
 
400
                          '}'])
 
401
 
 
402
        self.assertEqual(k.get(1),
 
403
                         ['foo {',
 
404
                          '  added in version 1',
 
405
                          '  also from v1',
 
406
                          '}'])
 
407
                       
 
408
        self.assertEqual(k.get(2),
 
409
                         ['foo {',
 
410
                          '  added in v2',
 
411
                          '}'])
 
412
 
 
413
        self.assertEqual(k.get(3),
 
414
                         ['foo {',
 
415
                          '  added in version 1',
 
416
                          '  added in v2',
 
417
                          '  also from v1',
 
418
                          '}'])
 
419
                         
 
420
 
 
421
 
 
422
class DeleteLines2(TestBase):
 
423
    """Test recording revisions that delete lines.
 
424
 
 
425
    This relies on the weave having a way to represent lines knocked
 
426
    out by a later revision."""
 
427
    def runTest(self):
 
428
        k = Weave()
 
429
 
 
430
        k.add([], ["line the first",
 
431
                   "line 2",
 
432
                   "line 3",
 
433
                   "fine"])
 
434
 
 
435
        self.assertEqual(len(k.get(0)), 4)
 
436
 
 
437
        k.add([0], ["line the first",
 
438
                   "fine"])
 
439
 
 
440
        self.assertEqual(k.get(1),
 
441
                         ["line the first",
 
442
                          "fine"])
 
443
 
 
444
        self.assertEqual(k.annotate(1),
 
445
                         [(0, "line the first"),
 
446
                          (0, "fine")])
 
447
 
 
448
 
 
449
 
 
450
class IncludeVersions(TestBase):
 
451
    """Check texts that are stored across multiple revisions.
 
452
 
 
453
    Here we manually create a weave with particular encoding and make
 
454
    sure it unpacks properly.
 
455
 
 
456
    Text 0 includes nothing; text 1 includes text 0 and adds some
 
457
    lines.
 
458
    """
 
459
 
 
460
    def runTest(self):
 
461
        k = Weave()
 
462
 
 
463
        k._v = [frozenset(), frozenset([0])]
 
464
        k._l = [('{', 0),
 
465
                "first line",
 
466
                ('}', 0),
 
467
                ('{', 1),
 
468
                "second line",
 
469
                ('}', 1)]
 
470
 
 
471
        self.assertEqual(k.get(1),
 
472
                         ["first line",
 
473
                          "second line"])
 
474
 
 
475
        self.assertEqual(k.get(0),
 
476
                         ["first line"])
 
477
 
 
478
        k.dump(self.TEST_LOG)
 
479
 
 
480
 
 
481
class DivergedIncludes(TestBase):
 
482
    """Weave with two diverged texts based on version 0.
 
483
    """
 
484
    def runTest(self):
 
485
        k = Weave()
 
486
 
 
487
        k._v = [frozenset(),
 
488
                frozenset([0]),
 
489
                frozenset([0]),
 
490
                ]
 
491
        k._l = [('{', 0),
 
492
                "first line",
 
493
                ('}', 0),
 
494
                ('{', 1),
 
495
                "second line",
 
496
                ('}', 1),
 
497
                ('{', 2),
 
498
                "alternative second line",
 
499
                ('}', 2),                
 
500
                ]
 
501
 
 
502
        self.assertEqual(k.get(0),
 
503
                         ["first line"])
 
504
 
 
505
        self.assertEqual(k.get(1),
 
506
                         ["first line",
 
507
                          "second line"])
 
508
 
 
509
        self.assertEqual(k.get(2),
 
510
                         ["first line",
 
511
                          "alternative second line"])
 
512
 
 
513
        self.assertEqual(k.inclusions([2]),
 
514
                         set([0, 2]))
 
515
 
 
516
 
 
517
 
 
518
class ReplaceLine(TestBase):
 
519
    def runTest(self):
 
520
        k = Weave()
 
521
 
 
522
        text0 = ['cheddar', 'stilton', 'gruyere']
 
523
        text1 = ['cheddar', 'blue vein', 'neufchatel', 'chevre']
 
524
        
 
525
        k.add([], text0)
 
526
        k.add([0], text1)
 
527
 
 
528
        self.log('k._l=' + pformat(k._l))
 
529
 
 
530
        self.assertEqual(k.get(0), text0)
 
531
        self.assertEqual(k.get(1), text1)
 
532
 
 
533
 
 
534
 
 
535
class Merge(TestBase):
 
536
    """Storage of versions that merge diverged parents"""
 
537
    def runTest(self):
 
538
        k = Weave()
 
539
 
 
540
        texts = [['header'],
 
541
                 ['header', '', 'line from 1'],
 
542
                 ['header', '', 'line from 2', 'more from 2'],
 
543
                 ['header', '', 'line from 1', 'fixup line', 'line from 2'],
 
544
                 ]
 
545
 
 
546
        k.add([], texts[0])
 
547
        k.add([0], texts[1])
 
548
        k.add([0], texts[2])
 
549
        k.add([0, 1, 2], texts[3])
 
550
 
 
551
        for i, t in enumerate(texts):
 
552
            self.assertEqual(k.get(i), t)
 
553
 
 
554
        self.assertEqual(k.annotate(3),
 
555
                         [(0, 'header'),
 
556
                          (1, ''),
 
557
                          (1, 'line from 1'),
 
558
                          (3, 'fixup line'),
 
559
                          (2, 'line from 2'),
 
560
                          ])
 
561
 
 
562
        self.assertEqual(k.inclusions([3]),
 
563
                         set([0, 1, 2, 3]))
 
564
 
 
565
        self.log('k._l=' + pformat(k._l))
 
566
 
 
567
        self.check_read_write(k)
 
568
 
 
569
 
 
570
class Conflicts(TestBase):
 
571
    """Test detection of conflicting regions during a merge.
 
572
 
 
573
    A base version is inserted, then two descendents try to
 
574
    insert different lines in the same place.  These should be
 
575
    reported as a possible conflict and forwarded to the user."""
 
576
    def runTest(self):
 
577
        return  # NOT RUN
 
578
        k = Weave()
 
579
 
 
580
        k.add([], ['aaa', 'bbb'])
 
581
        k.add([0], ['aaa', '111', 'bbb'])
 
582
        k.add([1], ['aaa', '222', 'bbb'])
 
583
 
 
584
        merged = k.merge([1, 2])
 
585
 
 
586
        self.assertEquals([[['aaa']],
 
587
                           [['111'], ['222']],
 
588
                           [['bbb']]])
 
589
 
 
590
 
 
591
 
 
592
class NonConflict(TestBase):
 
593
    """Two descendants insert compatible changes.
 
594
 
 
595
    No conflict should be reported."""
 
596
    def runTest(self):
 
597
        return  # NOT RUN
 
598
        k = Weave()
 
599
 
 
600
        k.add([], ['aaa', 'bbb'])
 
601
        k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
 
602
        k.add([1], ['aaa', 'ccc', 'bbb', '222'])
 
603
 
 
604
    
 
605
    
 
606
 
 
607
 
 
608
class AutoMerge(TestBase):
 
609
    def runTest(self):
 
610
        k = Weave()
 
611
 
 
612
        texts = [['header', 'aaa', 'bbb'],
 
613
                 ['header', 'aaa', 'line from 1', 'bbb'],
 
614
                 ['header', 'aaa', 'bbb', 'line from 2', 'more from 2'],
 
615
                 ]
 
616
 
 
617
        k.add([], texts[0])
 
618
        k.add([0], texts[1])
 
619
        k.add([0], texts[2])
 
620
 
 
621
        self.log('k._l=' + pformat(k._l))
 
622
 
 
623
        m = list(k.mash_iter([0, 1, 2]))
 
624
 
 
625
        self.assertEqual(m,
 
626
                         ['header', 'aaa',
 
627
                          'line from 1',
 
628
                          'bbb',
 
629
                          'line from 2', 'more from 2'])
 
630
        
 
631
 
 
632
 
 
633
class Khayyam(TestBase):
 
634
    """Test changes to multi-line texts, and read/write"""
 
635
    def runTest(self):
 
636
        rawtexts = [
 
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 enow!""",
 
641
            
 
642
            """A Book of Verses underneath the Bough,
 
643
            A Jug of Wine, a Loaf of Bread, -- and Thou
 
644
            Beside me singing in the Wilderness --
 
645
            Oh, Wilderness were Paradise now!""",
 
646
 
 
647
            """A Book of poems underneath the tree,
 
648
            A Jug of Wine, a Loaf of Bread,
 
649
            and Thou
 
650
            Beside me singing in the Wilderness --
 
651
            Oh, Wilderness were Paradise now!
 
652
 
 
653
            -- O. Khayyam""",
 
654
 
 
655
            """A Book of Verses underneath the Bough,
 
656
            A Jug of Wine, a Loaf of Bread,
 
657
            and Thou
 
658
            Beside me singing in the Wilderness --
 
659
            Oh, Wilderness were Paradise now!""",
 
660
            ]
 
661
        texts = [[l.strip() for l in t.split('\n')] for t in rawtexts]
 
662
 
 
663
        k = Weave()
 
664
        parents = set()
 
665
        for t in texts:
 
666
            ver = k.add(list(parents), t)
 
667
            parents.add(ver)
 
668
 
 
669
        self.log("k._l=" + pformat(k._l))
 
670
 
 
671
        for i, t in enumerate(texts):
 
672
            self.assertEqual(k.get(i), t)
 
673
 
 
674
        self.check_read_write(k)
 
675
 
 
676
 
 
677
def testweave():
 
678
    import testsweet
 
679
    from unittest import TestSuite, TestLoader
 
680
    import testweave
 
681
 
 
682
    tl = TestLoader()
 
683
    suite = TestSuite()
 
684
    suite.addTest(tl.loadTestsFromModule(testweave))
 
685
    
 
686
    return int(not testsweet.run_suite(suite)) # for shell 0=true
 
687
 
 
688
 
 
689
if __name__ == '__main__':
 
690
    import sys
 
691
    sys.exit(testweave())
 
692