~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_knit.py

  • Committer: Robert Collins
  • Date: 2006-03-01 08:40:35 UTC
  • mto: (1594.2.4 integration)
  • mto: This revision was merged to the branch mainline in revision 1596.
  • Revision ID: robertc@robertcollins.net-20060301084035-ce00abd11fe4da31
Change weave store to be a versioned store, using WeaveFiles which maintain integrity without needing explicit 'put' operations.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 by Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Tests for Knit data structure"""
 
18
 
 
19
 
 
20
import difflib
 
21
 
 
22
 
 
23
from bzrlib.errors import KnitError, RevisionAlreadyPresent
 
24
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory, KnitAnnotateFactory
 
25
from bzrlib.osutils import split_lines
 
26
from bzrlib.tests import TestCaseInTempDir
 
27
from bzrlib.transport.local import LocalTransport
 
28
from bzrlib.transactions import PassThroughTransaction
 
29
 
 
30
 
 
31
class KnitTests(TestCaseInTempDir):
 
32
 
 
33
    def add_stock_one_and_one_a(self, k):
 
34
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
35
        k.add_lines('text-1a', ['text-1'], split_lines(TEXT_1A))
 
36
 
 
37
    def test_knit_constructor(self):
 
38
        """Construct empty k"""
 
39
        knit = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
 
40
 
 
41
    def test_knit_add(self):
 
42
        """Store one text in knit and retrieve"""
 
43
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
 
44
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
45
        self.assertTrue(k.has_version('text-1'))
 
46
        self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
 
47
 
 
48
    def test_knit_reload(self):
 
49
        """Store and reload a knit"""
 
50
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
 
51
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
52
        del k
 
53
        k2 = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'r', KnitPlainFactory())
 
54
        self.assertTrue(k2.has_version('text-1'))
 
55
        self.assertEqualDiff(''.join(k2.get_lines('text-1')), TEXT_1)
 
56
 
 
57
    def test_knit_several(self):
 
58
        """Store several texts in a knit"""
 
59
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
 
60
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
61
        k.add_lines('text-2', [], split_lines(TEXT_2))
 
62
        self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
 
63
        self.assertEqualDiff(''.join(k.get_lines('text-2')), TEXT_2)
 
64
        
 
65
    def test_repeated_add(self):
 
66
        """Knit traps attempt to replace existing version"""
 
67
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
 
68
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
69
        self.assertRaises(RevisionAlreadyPresent, 
 
70
                k.add_lines,
 
71
                'text-1', [], split_lines(TEXT_1))
 
72
 
 
73
    def test_empty(self):
 
74
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
 
75
        k.add_lines('text-1', [], [])
 
76
        self.assertEquals(k.get_lines('text-1'), [])
 
77
 
 
78
    def test_incomplete(self):
 
79
        """Test if texts without a ending line-end can be inserted and
 
80
        extracted."""
 
81
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(),
 
82
             delta=False)
 
83
        k.add_lines('text-1', [], ['a\n',    'b'  ])
 
84
        k.add_lines('text-2', ['text-1'], ['a\rb\n', 'b\n'])
 
85
        self.assertEquals(k.get_lines('text-1'), ['a\n',    'b'  ])
 
86
        self.assertEquals(k.get_lines('text-2'), ['a\rb\n', 'b\n'])
 
87
 
 
88
    def test_delta(self):
 
89
        """Expression of knit delta as lines"""
 
90
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
 
91
        td = list(line_delta(TEXT_1.splitlines(True),
 
92
                             TEXT_1A.splitlines(True)))
 
93
        self.assertEqualDiff(''.join(td), delta_1_1a)
 
94
        out = apply_line_delta(TEXT_1.splitlines(True), td)
 
95
        self.assertEqualDiff(''.join(out), TEXT_1A)
 
96
 
 
97
    def test_add_with_parents(self):
 
98
        """Store in knit with parents"""
 
99
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
 
100
        self.add_stock_one_and_one_a(k)
 
101
        self.assertEquals(k.get_parents('text-1'), [])
 
102
        self.assertEquals(k.get_parents('text-1a'), ['text-1'])
 
103
 
 
104
    def test_ancestry(self):
 
105
        """Store in knit with parents"""
 
106
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
 
107
        self.add_stock_one_and_one_a(k)
 
108
        self.assertEquals(set(k.get_ancestry(['text-1a'])), set(['text-1a', 'text-1']))
 
109
 
 
110
    def test_add_delta(self):
 
111
        """Store in knit with parents"""
 
112
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(),
 
113
            delta=True)
 
114
        self.add_stock_one_and_one_a(k)
 
115
        self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
 
116
 
 
117
    def test_annotate(self):
 
118
        """Annotations"""
 
119
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(),
 
120
            delta=True)
 
121
        self.insert_and_test_small_annotate(k)
 
122
 
 
123
    def insert_and_test_small_annotate(self, k):
 
124
        """test annotation with k works correctly."""
 
125
        k.add_lines('text-1', [], ['a\n', 'b\n'])
 
126
        k.add_lines('text-2', ['text-1'], ['a\n', 'c\n'])
 
127
 
 
128
        origins = k.annotate('text-2')
 
129
        self.assertEquals(origins[0], ('text-1', 'a\n'))
 
130
        self.assertEquals(origins[1], ('text-2', 'c\n'))
 
131
 
 
132
    def test_annotate_fulltext(self):
 
133
        """Annotations"""
 
134
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(),
 
135
            delta=False)
 
136
        self.insert_and_test_small_annotate(k)
 
137
 
 
138
    def test_annotate_merge_1(self):
 
139
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
 
140
        k.add_lines('text-a1', [], ['a\n', 'b\n'])
 
141
        k.add_lines('text-a2', [], ['d\n', 'c\n'])
 
142
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['d\n', 'b\n'])
 
143
        origins = k.annotate('text-am')
 
144
        self.assertEquals(origins[0], ('text-a2', 'd\n'))
 
145
        self.assertEquals(origins[1], ('text-a1', 'b\n'))
 
146
 
 
147
    def test_annotate_merge_2(self):
 
148
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
 
149
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
150
        k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
 
151
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['a\n', 'y\n', 'c\n'])
 
152
        origins = k.annotate('text-am')
 
153
        self.assertEquals(origins[0], ('text-a1', 'a\n'))
 
154
        self.assertEquals(origins[1], ('text-a2', 'y\n'))
 
155
        self.assertEquals(origins[2], ('text-a1', 'c\n'))
 
156
 
 
157
    def test_annotate_merge_9(self):
 
158
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
 
159
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
160
        k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
 
161
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'c\n'])
 
162
        origins = k.annotate('text-am')
 
163
        self.assertEquals(origins[0], ('text-am', 'k\n'))
 
164
        self.assertEquals(origins[1], ('text-a2', 'y\n'))
 
165
        self.assertEquals(origins[2], ('text-a1', 'c\n'))
 
166
 
 
167
    def test_annotate_merge_3(self):
 
168
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
 
169
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
170
        k.add_lines('text-a2', [] ,['x\n', 'y\n', 'z\n'])
 
171
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'z\n'])
 
172
        origins = k.annotate('text-am')
 
173
        self.assertEquals(origins[0], ('text-am', 'k\n'))
 
174
        self.assertEquals(origins[1], ('text-a2', 'y\n'))
 
175
        self.assertEquals(origins[2], ('text-a2', 'z\n'))
 
176
 
 
177
    def test_annotate_merge_4(self):
 
178
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
 
179
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
180
        k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
 
181
        k.add_lines('text-a3', ['text-a1'], ['a\n', 'b\n', 'p\n'])
 
182
        k.add_lines('text-am', ['text-a2', 'text-a3'], ['a\n', 'b\n', 'z\n'])
 
183
        origins = k.annotate('text-am')
 
184
        self.assertEquals(origins[0], ('text-a1', 'a\n'))
 
185
        self.assertEquals(origins[1], ('text-a1', 'b\n'))
 
186
        self.assertEquals(origins[2], ('text-a2', 'z\n'))
 
187
 
 
188
    def test_annotate_merge_5(self):
 
189
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
 
190
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
191
        k.add_lines('text-a2', [], ['d\n', 'e\n', 'f\n'])
 
192
        k.add_lines('text-a3', [], ['x\n', 'y\n', 'z\n'])
 
193
        k.add_lines('text-am',
 
194
                    ['text-a1', 'text-a2', 'text-a3'],
 
195
                    ['a\n', 'e\n', 'z\n'])
 
196
        origins = k.annotate('text-am')
 
197
        self.assertEquals(origins[0], ('text-a1', 'a\n'))
 
198
        self.assertEquals(origins[1], ('text-a2', 'e\n'))
 
199
        self.assertEquals(origins[2], ('text-a3', 'z\n'))
 
200
 
 
201
    def test_annotate_file_cherry_pick(self):
 
202
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
 
203
        k.add_lines('text-1', [], ['a\n', 'b\n', 'c\n'])
 
204
        k.add_lines('text-2', ['text-1'], ['d\n', 'e\n', 'f\n'])
 
205
        k.add_lines('text-3', ['text-2', 'text-1'], ['a\n', 'b\n', 'c\n'])
 
206
        origins = k.annotate('text-3')
 
207
        self.assertEquals(origins[0], ('text-1', 'a\n'))
 
208
        self.assertEquals(origins[1], ('text-1', 'b\n'))
 
209
        self.assertEquals(origins[2], ('text-1', 'c\n'))
 
210
 
 
211
    def test_knit_join(self):
 
212
        """Store in knit with parents"""
 
213
        k1 = KnitVersionedFile(LocalTransport('.'), 'test1.knit', 'w', KnitPlainFactory())
 
214
        k1.add_lines('text-a', [], split_lines(TEXT_1))
 
215
        k1.add_lines('text-b', ['text-a'], split_lines(TEXT_1))
 
216
 
 
217
        k1.add_lines('text-c', [], split_lines(TEXT_1))
 
218
        k1.add_lines('text-d', ['text-c'], split_lines(TEXT_1))
 
219
 
 
220
        k1.add_lines('text-m', ['text-b', 'text-d'], split_lines(TEXT_1))
 
221
 
 
222
        k2 = KnitVersionedFile(LocalTransport('.'), 'test2.knit', 'w', KnitPlainFactory())
 
223
        count = k2.join(k1, version_ids=['text-m'])
 
224
        self.assertEquals(count, 5)
 
225
        self.assertTrue(k2.has_version('text-a'))
 
226
        self.assertTrue(k2.has_version('text-c'))
 
227
 
 
228
    def test_reannotate(self):
 
229
        k1 = KnitVersionedFile(LocalTransport('.'), 'test1', 'w',
 
230
                               KnitAnnotateFactory())
 
231
        # 0
 
232
        k1.add_lines('text-a', [], ['a\n', 'b\n'])
 
233
        # 1
 
234
        k1.add_lines('text-b', ['text-a'], ['a\n', 'c\n'])
 
235
 
 
236
        k2 = KnitVersionedFile(LocalTransport('.'), 'test2', 'w',
 
237
                               KnitAnnotateFactory())
 
238
        k2.join(k1, version_ids=['text-b'])
 
239
 
 
240
        # 2
 
241
        k1.add_lines('text-X', ['text-b'], ['a\n', 'b\n'])
 
242
        # 2
 
243
        k2.add_lines('text-c', ['text-b'], ['z\n', 'c\n'])
 
244
        # 3
 
245
        k2.add_lines('text-Y', ['text-b'], ['b\n', 'c\n'])
 
246
 
 
247
        # test-c will have index 3
 
248
        k1.join(k2, version_ids=['text-c'])
 
249
 
 
250
        lines = k1.get_lines('text-c')
 
251
        self.assertEquals(lines, ['z\n', 'c\n'])
 
252
 
 
253
        origins = k1.annotate('text-c')
 
254
        self.assertEquals(origins[0], ('text-c', 'z\n')) 
 
255
        self.assertEquals(origins[1], ('text-b', 'c\n')) 
 
256
 
 
257
 
 
258
TEXT_1 = """\
 
259
Banana cup cakes:
 
260
 
 
261
- bananas
 
262
- eggs
 
263
- broken tea cups
 
264
"""
 
265
 
 
266
TEXT_1A = """\
 
267
Banana cup cake recipe
 
268
(serves 6)
 
269
 
 
270
- bananas
 
271
- eggs
 
272
- broken tea cups
 
273
- self-raising flour
 
274
"""
 
275
 
 
276
delta_1_1a = """\
 
277
0,1,2
 
278
Banana cup cake recipe
 
279
(serves 6)
 
280
5,5,1
 
281
- self-raising flour
 
282
"""
 
283
 
 
284
TEXT_2 = """\
 
285
Boeuf bourguignon
 
286
 
 
287
- beef
 
288
- red wine
 
289
- small onions
 
290
- carrot
 
291
- mushrooms
 
292
"""
 
293
 
 
294
def line_delta(from_lines, to_lines):
 
295
    """Generate line-based delta from one text to another"""
 
296
    s = difflib.SequenceMatcher(None, from_lines, to_lines)
 
297
    for op in s.get_opcodes():
 
298
        if op[0] == 'equal':
 
299
            continue
 
300
        yield '%d,%d,%d\n' % (op[1], op[2], op[4]-op[3])
 
301
        for i in range(op[3], op[4]):
 
302
            yield to_lines[i]
 
303
 
 
304
 
 
305
def apply_line_delta(basis_lines, delta_lines):
 
306
    """Apply a line-based perfect diff
 
307
    
 
308
    basis_lines -- text to apply the patch to
 
309
    delta_lines -- diff instructions and content
 
310
    """
 
311
    out = basis_lines[:]
 
312
    i = 0
 
313
    offset = 0
 
314
    while i < len(delta_lines):
 
315
        l = delta_lines[i]
 
316
        a, b, c = map(long, l.split(','))
 
317
        i = i + 1
 
318
        out[offset+a:offset+b] = delta_lines[i:i+c]
 
319
        i = i + c
 
320
        offset = offset + (b - a) + c
 
321
    return out