~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-02-28 08:27:59 UTC
  • mto: (1594.2.4 integration)
  • mto: This revision was merged to the branch mainline in revision 1596.
  • Revision ID: robertc@robertcollins.net-20060228082759-d082181cb6fa27e5
First cut at including the knit implementation of versioned_file.

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
        self.t = PassThroughTransaction()
 
40
        knit = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
 
41
 
 
42
    def test_knit_add(self):
 
43
        """Store one text in knit and retrieve"""
 
44
        self.t = PassThroughTransaction()
 
45
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
 
46
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
47
        self.assertTrue(k.has_version('text-1'))
 
48
        self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
 
49
 
 
50
    def test_knit_reload(self):
 
51
        """Store and reload a knit"""
 
52
        self.t = PassThroughTransaction()
 
53
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
 
54
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
55
        del k
 
56
        k2 = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'r', KnitPlainFactory(), self.t)
 
57
        self.assertTrue(k2.has_version('text-1'))
 
58
        self.assertEqualDiff(''.join(k2.get_lines('text-1')), TEXT_1)
 
59
 
 
60
    def test_knit_several(self):
 
61
        """Store several texts in a knit"""
 
62
        self.t = PassThroughTransaction()
 
63
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
 
64
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
65
        k.add_lines('text-2', [], split_lines(TEXT_2))
 
66
        self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
 
67
        self.assertEqualDiff(''.join(k.get_lines('text-2')), TEXT_2)
 
68
        
 
69
    def test_repeated_add(self):
 
70
        """Knit traps attempt to replace existing version"""
 
71
        self.t = PassThroughTransaction()
 
72
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
 
73
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
74
        self.assertRaises(RevisionAlreadyPresent, 
 
75
                k.add_lines,
 
76
                'text-1', [], split_lines(TEXT_1))
 
77
 
 
78
    def test_empty(self):
 
79
        self.t = PassThroughTransaction()
 
80
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
 
81
        k.add_lines('text-1', [], [])
 
82
        self.assertEquals(k.get_lines('text-1'), [])
 
83
 
 
84
    def test_incomplete(self):
 
85
        """Test if texts without a ending line-end can be inserted and
 
86
        extracted."""
 
87
        self.t = PassThroughTransaction()
 
88
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t,
 
89
             delta=False)
 
90
        k.add_lines('text-1', [], ['a\n',    'b'  ])
 
91
        k.add_lines('text-2', ['text-1'], ['a\rb\n', 'b\n'])
 
92
        self.assertEquals(k.get_lines('text-1'), ['a\n',    'b'  ])
 
93
        self.assertEquals(k.get_lines('text-2'), ['a\rb\n', 'b\n'])
 
94
 
 
95
    def test_delta(self):
 
96
        """Expression of knit delta as lines"""
 
97
        self.t = PassThroughTransaction()
 
98
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
 
99
        td = list(line_delta(TEXT_1.splitlines(True),
 
100
                             TEXT_1A.splitlines(True)))
 
101
        self.assertEqualDiff(''.join(td), delta_1_1a)
 
102
        out = apply_line_delta(TEXT_1.splitlines(True), td)
 
103
        self.assertEqualDiff(''.join(out), TEXT_1A)
 
104
 
 
105
    def test_add_with_parents(self):
 
106
        """Store in knit with parents"""
 
107
        self.t = PassThroughTransaction()
 
108
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
 
109
        self.add_stock_one_and_one_a(k)
 
110
        self.assertEquals(k.get_parents('text-1'), [])
 
111
        self.assertEquals(k.get_parents('text-1a'), ['text-1'])
 
112
 
 
113
    def test_ancestry(self):
 
114
        """Store in knit with parents"""
 
115
        self.t = PassThroughTransaction()
 
116
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
 
117
        self.add_stock_one_and_one_a(k)
 
118
        self.assertEquals(set(k.get_ancestry(['text-1a'])), set(['text-1a', 'text-1']))
 
119
 
 
120
    def test_add_delta(self):
 
121
        """Store in knit with parents"""
 
122
        self.t = PassThroughTransaction()
 
123
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t,
 
124
            delta=True)
 
125
        self.add_stock_one_and_one_a(k)
 
126
        self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
 
127
 
 
128
    def test_annotate(self):
 
129
        """Annotations"""
 
130
        self.t = PassThroughTransaction()
 
131
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t,
 
132
            delta=True)
 
133
        self.insert_and_test_small_annotate(k)
 
134
 
 
135
    def insert_and_test_small_annotate(self, k):
 
136
        """test annotation with k works correctly."""
 
137
        k.add_lines('text-1', [], ['a\n', 'b\n'])
 
138
        k.add_lines('text-2', ['text-1'], ['a\n', 'c\n'])
 
139
 
 
140
        origins = k.annotate('text-2')
 
141
        self.assertEquals(origins[0], ('text-1', 'a\n'))
 
142
        self.assertEquals(origins[1], ('text-2', 'c\n'))
 
143
 
 
144
    def test_annotate_fulltext(self):
 
145
        """Annotations"""
 
146
        self.t = PassThroughTransaction()
 
147
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t,
 
148
            delta=False)
 
149
        self.insert_and_test_small_annotate(k)
 
150
 
 
151
    def test_annotate_merge_1(self):
 
152
        self.t = PassThroughTransaction()
 
153
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
 
154
        k.add_lines('text-a1', [], ['a\n', 'b\n'])
 
155
        k.add_lines('text-a2', [], ['d\n', 'c\n'])
 
156
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['d\n', 'b\n'])
 
157
        origins = k.annotate('text-am')
 
158
        self.assertEquals(origins[0], ('text-a2', 'd\n'))
 
159
        self.assertEquals(origins[1], ('text-a1', 'b\n'))
 
160
 
 
161
    def test_annotate_merge_2(self):
 
162
        self.t = PassThroughTransaction()
 
163
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
 
164
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
165
        k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
 
166
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['a\n', 'y\n', 'c\n'])
 
167
        origins = k.annotate('text-am')
 
168
        self.assertEquals(origins[0], ('text-a1', 'a\n'))
 
169
        self.assertEquals(origins[1], ('text-a2', 'y\n'))
 
170
        self.assertEquals(origins[2], ('text-a1', 'c\n'))
 
171
 
 
172
    def test_annotate_merge_9(self):
 
173
        self.t = PassThroughTransaction()
 
174
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
 
175
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
176
        k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
 
177
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'c\n'])
 
178
        origins = k.annotate('text-am')
 
179
        self.assertEquals(origins[0], ('text-am', 'k\n'))
 
180
        self.assertEquals(origins[1], ('text-a2', 'y\n'))
 
181
        self.assertEquals(origins[2], ('text-a1', 'c\n'))
 
182
 
 
183
    def test_annotate_merge_3(self):
 
184
        self.t = PassThroughTransaction()
 
185
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
 
186
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
187
        k.add_lines('text-a2', [] ,['x\n', 'y\n', 'z\n'])
 
188
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'z\n'])
 
189
        origins = k.annotate('text-am')
 
190
        self.assertEquals(origins[0], ('text-am', 'k\n'))
 
191
        self.assertEquals(origins[1], ('text-a2', 'y\n'))
 
192
        self.assertEquals(origins[2], ('text-a2', 'z\n'))
 
193
 
 
194
    def test_annotate_merge_4(self):
 
195
        self.t = PassThroughTransaction()
 
196
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
 
197
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
198
        k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
 
199
        k.add_lines('text-a3', ['text-a1'], ['a\n', 'b\n', 'p\n'])
 
200
        k.add_lines('text-am', ['text-a2', 'text-a3'], ['a\n', 'b\n', 'z\n'])
 
201
        origins = k.annotate('text-am')
 
202
        self.assertEquals(origins[0], ('text-a1', 'a\n'))
 
203
        self.assertEquals(origins[1], ('text-a1', 'b\n'))
 
204
        self.assertEquals(origins[2], ('text-a2', 'z\n'))
 
205
 
 
206
    def test_annotate_merge_5(self):
 
207
        self.t = PassThroughTransaction()
 
208
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
 
209
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
210
        k.add_lines('text-a2', [], ['d\n', 'e\n', 'f\n'])
 
211
        k.add_lines('text-a3', [], ['x\n', 'y\n', 'z\n'])
 
212
        k.add_lines('text-am',
 
213
                    ['text-a1', 'text-a2', 'text-a3'],
 
214
                    ['a\n', 'e\n', 'z\n'])
 
215
        origins = k.annotate('text-am')
 
216
        self.assertEquals(origins[0], ('text-a1', 'a\n'))
 
217
        self.assertEquals(origins[1], ('text-a2', 'e\n'))
 
218
        self.assertEquals(origins[2], ('text-a3', 'z\n'))
 
219
 
 
220
    def test_annotate_file_cherry_pick(self):
 
221
        self.t = PassThroughTransaction()
 
222
        k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
 
223
        k.add_lines('text-1', [], ['a\n', 'b\n', 'c\n'])
 
224
        k.add_lines('text-2', ['text-1'], ['d\n', 'e\n', 'f\n'])
 
225
        k.add_lines('text-3', ['text-2', 'text-1'], ['a\n', 'b\n', 'c\n'])
 
226
        origins = k.annotate('text-3')
 
227
        self.assertEquals(origins[0], ('text-1', 'a\n'))
 
228
        self.assertEquals(origins[1], ('text-1', 'b\n'))
 
229
        self.assertEquals(origins[2], ('text-1', 'c\n'))
 
230
 
 
231
    def test_knit_join(self):
 
232
        """Store in knit with parents"""
 
233
        self.t = PassThroughTransaction()
 
234
        k1 = KnitVersionedFile(LocalTransport('.'), 'test1.knit', 'w', KnitPlainFactory(), self.t)
 
235
        k1.add_lines('text-a', [], split_lines(TEXT_1))
 
236
        k1.add_lines('text-b', ['text-a'], split_lines(TEXT_1))
 
237
 
 
238
        k1.add_lines('text-c', [], split_lines(TEXT_1))
 
239
        k1.add_lines('text-d', ['text-c'], split_lines(TEXT_1))
 
240
 
 
241
        k1.add_lines('text-m', ['text-b', 'text-d'], split_lines(TEXT_1))
 
242
 
 
243
        k2 = KnitVersionedFile(LocalTransport('.'), 'test2.knit', 'w', KnitPlainFactory(), self.t)
 
244
        count = k2.join(k1, version_ids=['text-m'])
 
245
        self.assertEquals(count, 5)
 
246
        self.assertTrue(k2.has_version('text-a'))
 
247
        self.assertTrue(k2.has_version('text-c'))
 
248
 
 
249
    def test_reannotate(self):
 
250
        self.t = PassThroughTransaction()
 
251
        k1 = KnitVersionedFile(LocalTransport('.'), 'test1', 'w',
 
252
                               KnitAnnotateFactory(), self.t)
 
253
        # 0
 
254
        k1.add_lines('text-a', [], ['a\n', 'b\n'])
 
255
        # 1
 
256
        k1.add_lines('text-b', ['text-a'], ['a\n', 'c\n'])
 
257
 
 
258
        k2 = KnitVersionedFile(LocalTransport('.'), 'test2', 'w',
 
259
                               KnitAnnotateFactory(), self.t)
 
260
        k2.join(k1, version_ids=['text-b'])
 
261
 
 
262
        # 2
 
263
        k1.add_lines('text-X', ['text-b'], ['a\n', 'b\n'])
 
264
        # 2
 
265
        k2.add_lines('text-c', ['text-b'], ['z\n', 'c\n'])
 
266
        # 3
 
267
        k2.add_lines('text-Y', ['text-b'], ['b\n', 'c\n'])
 
268
 
 
269
        # test-c will have index 3
 
270
        k1.join(k2, version_ids=['text-c'])
 
271
 
 
272
        lines = k1.get_lines('text-c')
 
273
        self.assertEquals(lines, ['z\n', 'c\n'])
 
274
 
 
275
        origins = k1.annotate('text-c')
 
276
        self.assertEquals(origins[0], ('text-c', 'z\n')) 
 
277
        self.assertEquals(origins[1], ('text-b', 'c\n')) 
 
278
 
 
279
 
 
280
TEXT_1 = """\
 
281
Banana cup cakes:
 
282
 
 
283
- bananas
 
284
- eggs
 
285
- broken tea cups
 
286
"""
 
287
 
 
288
TEXT_1A = """\
 
289
Banana cup cake recipe
 
290
(serves 6)
 
291
 
 
292
- bananas
 
293
- eggs
 
294
- broken tea cups
 
295
- self-raising flour
 
296
"""
 
297
 
 
298
delta_1_1a = """\
 
299
0,1,2
 
300
Banana cup cake recipe
 
301
(serves 6)
 
302
5,5,1
 
303
- self-raising flour
 
304
"""
 
305
 
 
306
TEXT_2 = """\
 
307
Boeuf bourguignon
 
308
 
 
309
- beef
 
310
- red wine
 
311
- small onions
 
312
- carrot
 
313
- mushrooms
 
314
"""
 
315
 
 
316
def line_delta(from_lines, to_lines):
 
317
    """Generate line-based delta from one text to another"""
 
318
    s = difflib.SequenceMatcher(None, from_lines, to_lines)
 
319
    for op in s.get_opcodes():
 
320
        if op[0] == 'equal':
 
321
            continue
 
322
        yield '%d,%d,%d\n' % (op[1], op[2], op[4]-op[3])
 
323
        for i in range(op[3], op[4]):
 
324
            yield to_lines[i]
 
325
 
 
326
 
 
327
def apply_line_delta(basis_lines, delta_lines):
 
328
    """Apply a line-based perfect diff
 
329
    
 
330
    basis_lines -- text to apply the patch to
 
331
    delta_lines -- diff instructions and content
 
332
    """
 
333
    out = basis_lines[:]
 
334
    i = 0
 
335
    offset = 0
 
336
    while i < len(delta_lines):
 
337
        l = delta_lines[i]
 
338
        a, b, c = map(long, l.split(','))
 
339
        i = i + 1
 
340
        out[offset+a:offset+b] = delta_lines[i:i+c]
 
341
        i = i + c
 
342
        offset = offset + (b - a) + c
 
343
    return out