/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
1
# Copyright (C) 2005 by Canonical Ltd
2
#
3
# Authors:
4
#   Johan Rydberg <jrydberg@gnu.org>
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20
1664.2.9 by Aaron Bentley
Ported weave merge test to versionedfile
21
from StringIO import StringIO
22
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
23
import bzrlib
24
import bzrlib.errors as errors
1563.2.11 by Robert Collins
Consolidate reweave and join as we have no separate usage, make reweave tests apply to all versionedfile implementations and deprecate the old reweave apis.
25
from bzrlib.errors import (
26
                           RevisionNotPresent, 
27
                           RevisionAlreadyPresent,
28
                           WeaveParentMismatch
29
                           )
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
30
from bzrlib.knit import KnitVersionedFile, \
31
     KnitAnnotateFactory
1563.2.12 by Robert Collins
Checkpointing: created InterObject to factor out common inter object worker code, added InterVersionedFile and tests to allow making join work between any versionedfile.
32
from bzrlib.tests import TestCaseWithTransport
1666.1.1 by Robert Collins
Add trivial http-using test for versioned files.
33
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
34
from bzrlib.trace import mutter
1563.2.16 by Robert Collins
Change WeaveStore into VersionedFileStore and make its versoined file class parameterisable.
35
from bzrlib.transport import get_transport
1563.2.13 by Robert Collins
InterVersionedFile implemented.
36
from bzrlib.transport.memory import MemoryTransport
1563.2.12 by Robert Collins
Checkpointing: created InterObject to factor out common inter object worker code, added InterVersionedFile and tests to allow making join work between any versionedfile.
37
import bzrlib.versionedfile as versionedfile
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
38
from bzrlib.weave import WeaveFile
1664.2.9 by Aaron Bentley
Ported weave merge test to versionedfile
39
from bzrlib.weavefile import read_weave, write_weave
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
40
41
42
class VersionedFileTestMixIn(object):
43
    """A mixin test class for testing VersionedFiles.
44
45
    This is not an adaptor-style test at this point because
46
    theres no dynamic substitution of versioned file implementations,
47
    they are strictly controlled by their owning repositories.
48
    """
49
50
    def test_add(self):
51
        f = self.get_file()
52
        f.add_lines('r0', [], ['a\n', 'b\n'])
53
        f.add_lines('r1', ['r0'], ['b\n', 'c\n'])
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
54
        def verify_file(f):
55
            versions = f.versions()
56
            self.assertTrue('r0' in versions)
57
            self.assertTrue('r1' in versions)
58
            self.assertEquals(f.get_lines('r0'), ['a\n', 'b\n'])
59
            self.assertEquals(f.get_text('r0'), 'a\nb\n')
60
            self.assertEquals(f.get_lines('r1'), ['b\n', 'c\n'])
1563.2.18 by Robert Collins
get knit repositories really using knits for text storage.
61
            self.assertEqual(2, len(f))
62
            self.assertEqual(2, f.num_versions())
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
63
    
64
            self.assertRaises(RevisionNotPresent,
65
                f.add_lines, 'r2', ['foo'], [])
66
            self.assertRaises(RevisionAlreadyPresent,
67
                f.add_lines, 'r1', [], [])
68
        verify_file(f)
1666.1.6 by Robert Collins
Make knit the default format.
69
        # this checks that reopen with create=True does not break anything.
70
        f = self.reopen_file(create=True)
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
71
        verify_file(f)
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
72
1596.2.32 by Robert Collins
Reduce re-extraction of texts during weave to knit joins by providing a memoisation facility.
73
    def test_adds_with_parent_texts(self):
74
        f = self.get_file()
75
        parent_texts = {}
76
        parent_texts['r0'] = f.add_lines('r0', [], ['a\n', 'b\n'])
77
        try:
78
            parent_texts['r1'] = f.add_lines_with_ghosts('r1',
79
                                                         ['r0', 'ghost'], 
80
                                                         ['b\n', 'c\n'],
81
                                                         parent_texts=parent_texts)
82
        except NotImplementedError:
83
            # if the format doesn't support ghosts, just add normally.
84
            parent_texts['r1'] = f.add_lines('r1',
85
                                             ['r0'], 
86
                                             ['b\n', 'c\n'],
87
                                             parent_texts=parent_texts)
88
        f.add_lines('r2', ['r1'], ['c\n', 'd\n'], parent_texts=parent_texts)
89
        self.assertNotEqual(None, parent_texts['r0'])
90
        self.assertNotEqual(None, parent_texts['r1'])
91
        def verify_file(f):
92
            versions = f.versions()
93
            self.assertTrue('r0' in versions)
94
            self.assertTrue('r1' in versions)
95
            self.assertTrue('r2' in versions)
96
            self.assertEquals(f.get_lines('r0'), ['a\n', 'b\n'])
97
            self.assertEquals(f.get_lines('r1'), ['b\n', 'c\n'])
98
            self.assertEquals(f.get_lines('r2'), ['c\n', 'd\n'])
99
            self.assertEqual(3, f.num_versions())
100
            origins = f.annotate('r1')
101
            self.assertEquals(origins[0][0], 'r0')
102
            self.assertEquals(origins[1][0], 'r1')
103
            origins = f.annotate('r2')
104
            self.assertEquals(origins[0][0], 'r1')
105
            self.assertEquals(origins[1][0], 'r2')
106
107
        verify_file(f)
108
        f = self.reopen_file()
109
        verify_file(f)
110
1666.1.6 by Robert Collins
Make knit the default format.
111
    def test_add_unicode_content(self):
112
        # unicode content is not permitted in versioned files. 
113
        # versioned files version sequences of bytes only.
114
        vf = self.get_file()
115
        self.assertRaises(errors.BzrBadParameterUnicode,
116
            vf.add_lines, 'a', [], ['a\n', u'b\n', 'c\n'])
117
        self.assertRaises(
118
            (errors.BzrBadParameterUnicode, NotImplementedError),
119
            vf.add_lines_with_ghosts, 'a', [], ['a\n', u'b\n', 'c\n'])
120
121
    def test_inline_newline_throws(self):
122
        # \r characters are not permitted in lines being added
123
        vf = self.get_file()
124
        self.assertRaises(errors.BzrBadParameterContainsNewline, 
125
            vf.add_lines, 'a', [], ['a\n\n'])
126
        self.assertRaises(
127
            (errors.BzrBadParameterContainsNewline, NotImplementedError),
128
            vf.add_lines_with_ghosts, 'a', [], ['a\n\n'])
129
        # but inline CR's are allowed
130
        vf.add_lines('a', [], ['a\r\n'])
131
        try:
132
            vf.add_lines_with_ghosts('b', [], ['a\r\n'])
133
        except NotImplementedError:
134
            pass
135
1596.2.37 by Robert Collins
Switch to delta based content copying in the generic versioned file copier.
136
    def test_get_delta(self):
1596.2.36 by Robert Collins
add a get_delta api to versioned_file.
137
        f = self.get_file()
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
138
        sha1s = self._setup_for_deltas(f)
139
        expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False, 
140
                          [(0, 0, 1, [('base', 'line\n')])])
141
        self.assertEqual(expected_delta, f.get_delta('base'))
142
        next_parent = 'base'
143
        text_name = 'chain1-'
144
        for depth in range(26):
145
            new_version = text_name + '%s' % depth
146
            expected_delta = (next_parent, sha1s[depth], 
147
                              False,
148
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
149
            self.assertEqual(expected_delta, f.get_delta(new_version))
150
            next_parent = new_version
151
        next_parent = 'base'
152
        text_name = 'chain2-'
153
        for depth in range(26):
154
            new_version = text_name + '%s' % depth
155
            expected_delta = (next_parent, sha1s[depth], False,
156
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
157
            self.assertEqual(expected_delta, f.get_delta(new_version))
158
            next_parent = new_version
159
        # smoke test for eol support
160
        expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
161
        self.assertEqual(['line'], f.get_lines('noeol'))
162
        self.assertEqual(expected_delta, f.get_delta('noeol'))
163
164
    def test_get_deltas(self):
165
        f = self.get_file()
166
        sha1s = self._setup_for_deltas(f)
167
        deltas = f.get_deltas(f.versions())
168
        expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False, 
169
                          [(0, 0, 1, [('base', 'line\n')])])
170
        self.assertEqual(expected_delta, deltas['base'])
171
        next_parent = 'base'
172
        text_name = 'chain1-'
173
        for depth in range(26):
174
            new_version = text_name + '%s' % depth
175
            expected_delta = (next_parent, sha1s[depth], 
176
                              False,
177
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
178
            self.assertEqual(expected_delta, deltas[new_version])
179
            next_parent = new_version
180
        next_parent = 'base'
181
        text_name = 'chain2-'
182
        for depth in range(26):
183
            new_version = text_name + '%s' % depth
184
            expected_delta = (next_parent, sha1s[depth], False,
185
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
186
            self.assertEqual(expected_delta, deltas[new_version])
187
            next_parent = new_version
188
        # smoke tests for eol support
189
        expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
190
        self.assertEqual(['line'], f.get_lines('noeol'))
191
        self.assertEqual(expected_delta, deltas['noeol'])
192
        # smoke tests for eol support - two noeol in a row same content
193
        expected_deltas = (('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True, 
194
                          [(0, 1, 2, [(u'noeolsecond', 'line\n'), (u'noeolsecond', 'line\n')])]),
195
                          ('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True, 
196
                           [(0, 0, 1, [('noeolsecond', 'line\n')]), (1, 1, 0, [])]))
197
        self.assertEqual(['line\n', 'line'], f.get_lines('noeolsecond'))
198
        self.assertTrue(deltas['noeolsecond'] in expected_deltas)
199
        # two no-eol in a row, different content
200
        expected_delta = ('noeolsecond', '8bb553a84e019ef1149db082d65f3133b195223b', True, 
201
                          [(1, 2, 1, [(u'noeolnotshared', 'phone\n')])])
202
        self.assertEqual(['line\n', 'phone'], f.get_lines('noeolnotshared'))
203
        self.assertEqual(expected_delta, deltas['noeolnotshared'])
204
        # eol folling a no-eol with content change
205
        expected_delta = ('noeol', 'a61f6fb6cfc4596e8d88c34a308d1e724caf8977', False, 
206
                          [(0, 1, 1, [(u'eol', 'phone\n')])])
207
        self.assertEqual(['phone\n'], f.get_lines('eol'))
208
        self.assertEqual(expected_delta, deltas['eol'])
209
        # eol folling a no-eol with content change
210
        expected_delta = ('noeol', '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False, 
211
                          [(0, 1, 1, [(u'eolline', 'line\n')])])
212
        self.assertEqual(['line\n'], f.get_lines('eolline'))
213
        self.assertEqual(expected_delta, deltas['eolline'])
214
        # eol with no parents
215
        expected_delta = (None, '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, 
216
                          [(0, 0, 1, [(u'noeolbase', 'line\n')])])
217
        self.assertEqual(['line'], f.get_lines('noeolbase'))
218
        self.assertEqual(expected_delta, deltas['noeolbase'])
219
        # eol with two parents, in inverse insertion order
220
        expected_deltas = (('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
221
                            [(0, 1, 1, [(u'eolbeforefirstparent', 'line\n')])]),
222
                           ('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
223
                            [(0, 1, 1, [(u'eolbeforefirstparent', 'line\n')])]))
224
        self.assertEqual(['line'], f.get_lines('eolbeforefirstparent'))
225
        #self.assertTrue(deltas['eolbeforefirstparent'] in expected_deltas)
226
227
    def _setup_for_deltas(self, f):
1596.2.36 by Robert Collins
add a get_delta api to versioned_file.
228
        self.assertRaises(errors.RevisionNotPresent, f.get_delta, 'base')
229
        # add texts that should trip the knit maximum delta chain threshold
230
        # as well as doing parallel chains of data in knits.
231
        # this is done by two chains of 25 insertions
232
        f.add_lines('base', [], ['line\n'])
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
233
        f.add_lines('noeol', ['base'], ['line'])
234
        # detailed eol tests:
235
        # shared last line with parent no-eol
236
        f.add_lines('noeolsecond', ['noeol'], ['line\n', 'line'])
237
        # differing last line with parent, both no-eol
238
        f.add_lines('noeolnotshared', ['noeolsecond'], ['line\n', 'phone'])
239
        # add eol following a noneol parent, change content
240
        f.add_lines('eol', ['noeol'], ['phone\n'])
241
        # add eol following a noneol parent, no change content
242
        f.add_lines('eolline', ['noeol'], ['line\n'])
243
        # noeol with no parents:
244
        f.add_lines('noeolbase', [], ['line'])
245
        # noeol preceeding its leftmost parent in the output:
246
        # this is done by making it a merge of two parents with no common
247
        # anestry: noeolbase and noeol with the 
248
        # later-inserted parent the leftmost.
249
        f.add_lines('eolbeforefirstparent', ['noeolbase', 'noeol'], ['line'])
250
        # two identical eol texts
251
        f.add_lines('noeoldup', ['noeol'], ['line'])
1596.2.36 by Robert Collins
add a get_delta api to versioned_file.
252
        next_parent = 'base'
253
        text_name = 'chain1-'
254
        text = ['line\n']
255
        sha1s = {0 :'da6d3141cb4a5e6f464bf6e0518042ddc7bfd079',
256
                 1 :'45e21ea146a81ea44a821737acdb4f9791c8abe7',
257
                 2 :'e1f11570edf3e2a070052366c582837a4fe4e9fa',
258
                 3 :'26b4b8626da827088c514b8f9bbe4ebf181edda1',
259
                 4 :'e28a5510be25ba84d31121cff00956f9970ae6f6',
260
                 5 :'d63ec0ce22e11dcf65a931b69255d3ac747a318d',
261
                 6 :'2c2888d288cb5e1d98009d822fedfe6019c6a4ea',
262
                 7 :'95c14da9cafbf828e3e74a6f016d87926ba234ab',
263
                 8 :'779e9a0b28f9f832528d4b21e17e168c67697272',
264
                 9 :'1f8ff4e5c6ff78ac106fcfe6b1e8cb8740ff9a8f',
265
                 10:'131a2ae712cf51ed62f143e3fbac3d4206c25a05',
266
                 11:'c5a9d6f520d2515e1ec401a8f8a67e6c3c89f199',
267
                 12:'31a2286267f24d8bedaa43355f8ad7129509ea85',
268
                 13:'dc2a7fe80e8ec5cae920973973a8ee28b2da5e0a',
269
                 14:'2c4b1736566b8ca6051e668de68650686a3922f2',
270
                 15:'5912e4ecd9b0c07be4d013e7e2bdcf9323276cde',
271
                 16:'b0d2e18d3559a00580f6b49804c23fea500feab3',
272
                 17:'8e1d43ad72f7562d7cb8f57ee584e20eb1a69fc7',
273
                 18:'5cf64a3459ae28efa60239e44b20312d25b253f3',
274
                 19:'1ebed371807ba5935958ad0884595126e8c4e823',
275
                 20:'2aa62a8b06fb3b3b892a3292a068ade69d5ee0d3',
276
                 21:'01edc447978004f6e4e962b417a4ae1955b6fe5d',
277
                 22:'d8d8dc49c4bf0bab401e0298bb5ad827768618bb',
278
                 23:'c21f62b1c482862983a8ffb2b0c64b3451876e3f',
279
                 24:'c0593fe795e00dff6b3c0fe857a074364d5f04fc',
280
                 25:'dd1a1cf2ba9cc225c3aff729953e6364bf1d1855',
281
                 }
282
        for depth in range(26):
283
            new_version = text_name + '%s' % depth
284
            text = text + ['line\n']
285
            f.add_lines(new_version, [next_parent], text)
286
            next_parent = new_version
287
        next_parent = 'base'
288
        text_name = 'chain2-'
289
        text = ['line\n']
290
        for depth in range(26):
291
            new_version = text_name + '%s' % depth
292
            text = text + ['line\n']
293
            f.add_lines(new_version, [next_parent], text)
294
            next_parent = new_version
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
295
        return sha1s
1596.2.37 by Robert Collins
Switch to delta based content copying in the generic versioned file copier.
296
297
    def test_add_delta(self):
298
        # tests for the add-delta facility.
299
        # at this point, optimising for speed, we assume no checks when deltas are inserted.
300
        # this may need to be revisited.
301
        source = self.get_file('source')
302
        source.add_lines('base', [], ['line\n'])
303
        next_parent = 'base'
304
        text_name = 'chain1-'
305
        text = ['line\n']
306
        for depth in range(26):
307
            new_version = text_name + '%s' % depth
308
            text = text + ['line\n']
309
            source.add_lines(new_version, [next_parent], text)
310
            next_parent = new_version
311
        next_parent = 'base'
312
        text_name = 'chain2-'
313
        text = ['line\n']
314
        for depth in range(26):
315
            new_version = text_name + '%s' % depth
316
            text = text + ['line\n']
317
            source.add_lines(new_version, [next_parent], text)
318
            next_parent = new_version
319
        source.add_lines('noeol', ['base'], ['line'])
320
        
321
        target = self.get_file('target')
322
        for version in source.versions():
323
            parent, sha1, noeol, delta = source.get_delta(version)
324
            target.add_delta(version,
325
                             source.get_parents(version),
326
                             parent,
327
                             sha1,
328
                             noeol,
329
                             delta)
330
        self.assertRaises(RevisionAlreadyPresent,
331
                          target.add_delta, 'base', [], None, '', False, [])
332
        for version in source.versions():
333
            self.assertEqual(source.get_lines(version),
334
                             target.get_lines(version))
1596.2.36 by Robert Collins
add a get_delta api to versioned_file.
335
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
336
    def test_ancestry(self):
337
        f = self.get_file()
1563.2.29 by Robert Collins
Remove all but fetch references to repository.revision_store.
338
        self.assertEqual([], f.get_ancestry([]))
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
339
        f.add_lines('r0', [], ['a\n', 'b\n'])
340
        f.add_lines('r1', ['r0'], ['b\n', 'c\n'])
341
        f.add_lines('r2', ['r0'], ['b\n', 'c\n'])
342
        f.add_lines('r3', ['r2'], ['b\n', 'c\n'])
343
        f.add_lines('rM', ['r1', 'r2'], ['b\n', 'c\n'])
1563.2.29 by Robert Collins
Remove all but fetch references to repository.revision_store.
344
        self.assertEqual([], f.get_ancestry([]))
1563.2.35 by Robert Collins
cleanup deprecation warnings and finish conversion so the inventory is knit based too.
345
        versions = f.get_ancestry(['rM'])
346
        # there are some possibilities:
347
        # r0 r1 r2 rM r3
348
        # r0 r1 r2 r3 rM
349
        # etc
350
        # so we check indexes
351
        r0 = versions.index('r0')
352
        r1 = versions.index('r1')
353
        r2 = versions.index('r2')
354
        self.assertFalse('r3' in versions)
355
        rM = versions.index('rM')
356
        self.assertTrue(r0 < r1)
357
        self.assertTrue(r0 < r2)
358
        self.assertTrue(r1 < rM)
359
        self.assertTrue(r2 < rM)
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
360
361
        self.assertRaises(RevisionNotPresent,
362
            f.get_ancestry, ['rM', 'rX'])
1594.2.21 by Robert Collins
Teach versioned files to prevent mutation after finishing.
363
364
    def test_mutate_after_finish(self):
365
        f = self.get_file()
366
        f.transaction_finished()
1596.2.37 by Robert Collins
Switch to delta based content copying in the generic versioned file copier.
367
        self.assertRaises(errors.OutSideTransaction, f.add_delta, '', [], '', '', False, [])
1594.2.21 by Robert Collins
Teach versioned files to prevent mutation after finishing.
368
        self.assertRaises(errors.OutSideTransaction, f.add_lines, '', [], [])
369
        self.assertRaises(errors.OutSideTransaction, f.add_lines_with_ghosts, '', [], [])
370
        self.assertRaises(errors.OutSideTransaction, f.fix_parents, '', [])
371
        self.assertRaises(errors.OutSideTransaction, f.join, '')
1594.2.24 by Robert Collins
Make use of the transaction finalisation warning support to implement in-knit caching.
372
        self.assertRaises(errors.OutSideTransaction, f.clone_text, 'base', 'bar', ['foo'])
1563.2.7 by Robert Collins
add versioned file clear_cache entry.
373
        
374
    def test_clear_cache(self):
375
        f = self.get_file()
376
        # on a new file it should not error
377
        f.clear_cache()
378
        # and after adding content, doing a clear_cache and a get should work.
379
        f.add_lines('0', [], ['a'])
380
        f.clear_cache()
381
        self.assertEqual(['a'], f.get_lines('0'))
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
382
383
    def test_clone_text(self):
384
        f = self.get_file()
385
        f.add_lines('r0', [], ['a\n', 'b\n'])
1563.2.5 by Robert Collins
Remove unused transaction references from knit.py and the versionedfile interface.
386
        f.clone_text('r1', 'r0', ['r0'])
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
387
        def verify_file(f):
388
            self.assertEquals(f.get_lines('r1'), f.get_lines('r0'))
389
            self.assertEquals(f.get_lines('r1'), ['a\n', 'b\n'])
390
            self.assertEquals(f.get_parents('r1'), ['r0'])
391
    
392
            self.assertRaises(RevisionNotPresent,
393
                f.clone_text, 'r2', 'rX', [])
394
            self.assertRaises(RevisionAlreadyPresent,
395
                f.clone_text, 'r1', 'r0', [])
396
        verify_file(f)
397
        verify_file(self.reopen_file())
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
398
1563.2.13 by Robert Collins
InterVersionedFile implemented.
399
    def test_create_empty(self):
400
        f = self.get_file()
401
        f.add_lines('0', [], ['a\n'])
402
        new_f = f.create_empty('t', MemoryTransport())
403
        # smoke test, specific types should check it is honoured correctly for
404
        # non type attributes
405
        self.assertEqual([], new_f.versions())
406
        self.assertTrue(isinstance(new_f, f.__class__))
407
1563.2.15 by Robert Collins
remove the weavestore assumptions about the number and nature of files it manages.
408
    def test_copy_to(self):
409
        f = self.get_file()
410
        f.add_lines('0', [], ['a\n'])
411
        t = MemoryTransport()
412
        f.copy_to('foo', t)
413
        for suffix in f.__class__.get_suffixes():
414
            self.assertTrue(t.has('foo' + suffix))
415
416
    def test_get_suffixes(self):
417
        f = self.get_file()
418
        # should be the same
419
        self.assertEqual(f.__class__.get_suffixes(), f.__class__.get_suffixes())
420
        # and should be a list
421
        self.assertTrue(isinstance(f.__class__.get_suffixes(), list))
422
1563.2.13 by Robert Collins
InterVersionedFile implemented.
423
    def test_get_graph(self):
424
        f = self.get_file()
425
        f.add_lines('v1', [], ['hello\n'])
426
        f.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
427
        f.add_lines('v3', ['v2'], ['hello\n', 'cruel\n', 'world\n'])
428
        self.assertEqual({'v1': [],
429
                          'v2': ['v1'],
430
                          'v3': ['v2']},
431
                         f.get_graph())
432
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
433
    def test_get_parents(self):
434
        f = self.get_file()
435
        f.add_lines('r0', [], ['a\n', 'b\n'])
436
        f.add_lines('r1', [], ['a\n', 'b\n'])
437
        f.add_lines('r2', [], ['a\n', 'b\n'])
438
        f.add_lines('r3', [], ['a\n', 'b\n'])
439
        f.add_lines('m', ['r0', 'r1', 'r2', 'r3'], ['a\n', 'b\n'])
440
        self.assertEquals(f.get_parents('m'), ['r0', 'r1', 'r2', 'r3'])
441
442
        self.assertRaises(RevisionNotPresent,
443
            f.get_parents, 'y')
444
445
    def test_annotate(self):
446
        f = self.get_file()
447
        f.add_lines('r0', [], ['a\n', 'b\n'])
448
        f.add_lines('r1', ['r0'], ['c\n', 'b\n'])
449
        origins = f.annotate('r1')
450
        self.assertEquals(origins[0][0], 'r1')
451
        self.assertEquals(origins[1][0], 'r0')
452
453
        self.assertRaises(RevisionNotPresent,
454
            f.annotate, 'foo')
455
456
    def test_walk(self):
1563.2.35 by Robert Collins
cleanup deprecation warnings and finish conversion so the inventory is knit based too.
457
        # tests that walk returns all the inclusions for the requested
458
        # revisions as well as the revisions changes themselves.
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
459
        f = self.get_file('1')
460
        f.add_lines('r0', [], ['a\n', 'b\n'])
461
        f.add_lines('r1', ['r0'], ['c\n', 'b\n'])
462
        f.add_lines('rX', ['r1'], ['d\n', 'b\n'])
463
        f.add_lines('rY', ['r1'], ['c\n', 'e\n'])
464
465
        lines = {}
466
        for lineno, insert, dset, text in f.walk(['rX', 'rY']):
467
            lines[text] = (insert, dset)
468
469
        self.assertTrue(lines['a\n'], ('r0', set(['r1'])))
470
        self.assertTrue(lines['b\n'], ('r0', set(['rY'])))
471
        self.assertTrue(lines['c\n'], ('r1', set(['rX'])))
472
        self.assertTrue(lines['d\n'], ('rX', set([])))
473
        self.assertTrue(lines['e\n'], ('rY', set([])))
474
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
475
    def test_detection(self):
476
        # Test weaves detect corruption.
477
        #
478
        # Weaves contain a checksum of their texts.
479
        # When a text is extracted, this checksum should be
480
        # verified.
481
482
        w = self.get_file_corrupted_text()
483
484
        self.assertEqual('hello\n', w.get_text('v1'))
485
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
486
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
487
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
488
489
        w = self.get_file_corrupted_checksum()
490
491
        self.assertEqual('hello\n', w.get_text('v1'))
492
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
493
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
494
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
495
496
    def get_file_corrupted_text(self):
497
        """Return a versioned file with corrupt text but valid metadata."""
498
        raise NotImplementedError(self.get_file_corrupted_text)
499
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
500
    def reopen_file(self, name='foo'):
501
        """Open the versioned file from disk again."""
502
        raise NotImplementedError(self.reopen_file)
503
1594.2.6 by Robert Collins
Introduce a api specifically for looking at lines in some versions of the inventory, for fileid_involved.
504
    def test_iter_lines_added_or_present_in_versions(self):
505
        # test that we get at least an equalset of the lines added by
506
        # versions in the weave 
507
        # the ordering here is to make a tree so that dumb searches have
508
        # more changes to muck up.
509
        vf = self.get_file()
510
        # add a base to get included
511
        vf.add_lines('base', [], ['base\n'])
512
        # add a ancestor to be included on one side
513
        vf.add_lines('lancestor', [], ['lancestor\n'])
514
        # add a ancestor to be included on the other side
515
        vf.add_lines('rancestor', ['base'], ['rancestor\n'])
516
        # add a child of rancestor with no eofile-nl
517
        vf.add_lines('child', ['rancestor'], ['base\n', 'child\n'])
518
        # add a child of lancestor and base to join the two roots
519
        vf.add_lines('otherchild',
520
                     ['lancestor', 'base'],
521
                     ['base\n', 'lancestor\n', 'otherchild\n'])
522
        def iter_with_versions(versions):
523
            # now we need to see what lines are returned, and how often.
524
            lines = {'base\n':0,
525
                     'lancestor\n':0,
526
                     'rancestor\n':0,
527
                     'child\n':0,
528
                     'otherchild\n':0,
529
                     }
530
            # iterate over the lines
531
            for line in vf.iter_lines_added_or_present_in_versions(versions):
532
                lines[line] += 1
533
            return lines
534
        lines = iter_with_versions(['child', 'otherchild'])
535
        # we must see child and otherchild
536
        self.assertTrue(lines['child\n'] > 0)
537
        self.assertTrue(lines['otherchild\n'] > 0)
538
        # we dont care if we got more than that.
539
        
540
        # test all lines
541
        lines = iter_with_versions(None)
542
        # all lines must be seen at least once
543
        self.assertTrue(lines['base\n'] > 0)
544
        self.assertTrue(lines['lancestor\n'] > 0)
545
        self.assertTrue(lines['rancestor\n'] > 0)
546
        self.assertTrue(lines['child\n'] > 0)
547
        self.assertTrue(lines['otherchild\n'] > 0)
1594.2.7 by Robert Collins
Add versionedfile.fix_parents api for correcting data post hoc.
548
549
    def test_fix_parents(self):
550
        # some versioned files allow incorrect parents to be corrected after
551
        # insertion - this may not fix ancestry..
552
        # if they do not supported, they just do not implement it.
1594.2.8 by Robert Collins
add ghost aware apis to knits.
553
        # we test this as an interface test to ensure that those that *do*
554
        # implementent it get it right.
1594.2.7 by Robert Collins
Add versionedfile.fix_parents api for correcting data post hoc.
555
        vf = self.get_file()
556
        vf.add_lines('notbase', [], [])
557
        vf.add_lines('base', [], [])
558
        try:
559
            vf.fix_parents('notbase', ['base'])
560
        except NotImplementedError:
561
            return
562
        self.assertEqual(['base'], vf.get_parents('notbase'))
563
        # open again, check it stuck.
564
        vf = self.get_file()
565
        self.assertEqual(['base'], vf.get_parents('notbase'))
566
1594.2.8 by Robert Collins
add ghost aware apis to knits.
567
    def test_fix_parents_with_ghosts(self):
568
        # when fixing parents, ghosts that are listed should not be ghosts
569
        # anymore.
570
        vf = self.get_file()
571
572
        try:
573
            vf.add_lines_with_ghosts('notbase', ['base', 'stillghost'], [])
574
        except NotImplementedError:
575
            return
576
        vf.add_lines('base', [], [])
577
        vf.fix_parents('notbase', ['base', 'stillghost'])
578
        self.assertEqual(['base'], vf.get_parents('notbase'))
579
        # open again, check it stuck.
580
        vf = self.get_file()
581
        self.assertEqual(['base'], vf.get_parents('notbase'))
582
        # and check the ghosts
583
        self.assertEqual(['base', 'stillghost'],
584
                         vf.get_parents_with_ghosts('notbase'))
585
586
    def test_add_lines_with_ghosts(self):
587
        # some versioned file formats allow lines to be added with parent
588
        # information that is > than that in the format. Formats that do
589
        # not support this need to raise NotImplementedError on the
590
        # add_lines_with_ghosts api.
591
        vf = self.get_file()
592
        # add a revision with ghost parents
593
        try:
1596.2.9 by Robert Collins
Utf8 safety in knit indexes.
594
            vf.add_lines_with_ghosts(u'notbxbfse', [u'b\xbfse'], [])
1594.2.8 by Robert Collins
add ghost aware apis to knits.
595
        except NotImplementedError:
596
            # check the other ghost apis are also not implemented
597
            self.assertRaises(NotImplementedError, vf.has_ghost, 'foo')
598
            self.assertRaises(NotImplementedError, vf.get_ancestry_with_ghosts, ['foo'])
599
            self.assertRaises(NotImplementedError, vf.get_parents_with_ghosts, 'foo')
600
            self.assertRaises(NotImplementedError, vf.get_graph_with_ghosts)
601
            return
602
        # test key graph related apis: getncestry, _graph, get_parents
603
        # has_version
604
        # - these are ghost unaware and must not be reflect ghosts
1596.2.9 by Robert Collins
Utf8 safety in knit indexes.
605
        self.assertEqual([u'notbxbfse'], vf.get_ancestry(u'notbxbfse'))
606
        self.assertEqual([], vf.get_parents(u'notbxbfse'))
607
        self.assertEqual({u'notbxbfse':[]}, vf.get_graph())
608
        self.assertFalse(vf.has_version(u'b\xbfse'))
1594.2.8 by Robert Collins
add ghost aware apis to knits.
609
        # we have _with_ghost apis to give us ghost information.
1596.2.9 by Robert Collins
Utf8 safety in knit indexes.
610
        self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry_with_ghosts([u'notbxbfse']))
611
        self.assertEqual([u'b\xbfse'], vf.get_parents_with_ghosts(u'notbxbfse'))
612
        self.assertEqual({u'notbxbfse':[u'b\xbfse']}, vf.get_graph_with_ghosts())
613
        self.assertTrue(vf.has_ghost(u'b\xbfse'))
1594.2.8 by Robert Collins
add ghost aware apis to knits.
614
        # if we add something that is a ghost of another, it should correct the
615
        # results of the prior apis
1596.2.9 by Robert Collins
Utf8 safety in knit indexes.
616
        vf.add_lines(u'b\xbfse', [], [])
617
        self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry([u'notbxbfse']))
618
        self.assertEqual([u'b\xbfse'], vf.get_parents(u'notbxbfse'))
619
        self.assertEqual({u'b\xbfse':[],
620
                          u'notbxbfse':[u'b\xbfse'],
1594.2.8 by Robert Collins
add ghost aware apis to knits.
621
                          },
622
                         vf.get_graph())
1596.2.9 by Robert Collins
Utf8 safety in knit indexes.
623
        self.assertTrue(vf.has_version(u'b\xbfse'))
1594.2.8 by Robert Collins
add ghost aware apis to knits.
624
        # we have _with_ghost apis to give us ghost information.
1596.2.9 by Robert Collins
Utf8 safety in knit indexes.
625
        self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry_with_ghosts([u'notbxbfse']))
626
        self.assertEqual([u'b\xbfse'], vf.get_parents_with_ghosts(u'notbxbfse'))
627
        self.assertEqual({u'b\xbfse':[],
628
                          u'notbxbfse':[u'b\xbfse'],
1594.2.8 by Robert Collins
add ghost aware apis to knits.
629
                          },
630
                         vf.get_graph_with_ghosts())
1596.2.9 by Robert Collins
Utf8 safety in knit indexes.
631
        self.assertFalse(vf.has_ghost(u'b\xbfse'))
1594.2.8 by Robert Collins
add ghost aware apis to knits.
632
1594.2.9 by Robert Collins
Teach Knit repositories how to handle ghosts without corrupting at all.
633
    def test_add_lines_with_ghosts_after_normal_revs(self):
634
        # some versioned file formats allow lines to be added with parent
635
        # information that is > than that in the format. Formats that do
636
        # not support this need to raise NotImplementedError on the
637
        # add_lines_with_ghosts api.
638
        vf = self.get_file()
639
        # probe for ghost support
640
        try:
641
            vf.has_ghost('hoo')
642
        except NotImplementedError:
643
            return
644
        vf.add_lines_with_ghosts('base', [], ['line\n', 'line_b\n'])
645
        vf.add_lines_with_ghosts('references_ghost',
646
                                 ['base', 'a_ghost'],
647
                                 ['line\n', 'line_b\n', 'line_c\n'])
648
        origins = vf.annotate('references_ghost')
649
        self.assertEquals(('base', 'line\n'), origins[0])
650
        self.assertEquals(('base', 'line_b\n'), origins[1])
651
        self.assertEquals(('references_ghost', 'line_c\n'), origins[2])
1594.2.23 by Robert Collins
Test versioned file storage handling of clean/dirty status for accessed versioned files.
652
653
    def test_readonly_mode(self):
654
        transport = get_transport(self.get_url('.'))
655
        factory = self.get_factory()
656
        vf = factory('id', transport, 0777, create=True, access_mode='w')
657
        vf = factory('id', transport, access_mode='r')
1596.2.37 by Robert Collins
Switch to delta based content copying in the generic versioned file copier.
658
        self.assertRaises(errors.ReadOnlyError, vf.add_delta, '', [], '', '', False, [])
1594.2.23 by Robert Collins
Test versioned file storage handling of clean/dirty status for accessed versioned files.
659
        self.assertRaises(errors.ReadOnlyError, vf.add_lines, 'base', [], [])
660
        self.assertRaises(errors.ReadOnlyError,
661
                          vf.add_lines_with_ghosts,
662
                          'base',
663
                          [],
664
                          [])
665
        self.assertRaises(errors.ReadOnlyError, vf.fix_parents, 'base', [])
666
        self.assertRaises(errors.ReadOnlyError, vf.join, 'base')
1594.2.24 by Robert Collins
Make use of the transaction finalisation warning support to implement in-knit caching.
667
        self.assertRaises(errors.ReadOnlyError, vf.clone_text, 'base', 'bar', ['foo'])
1666.1.6 by Robert Collins
Make knit the default format.
668
    
669
    def test_get_sha1(self):
670
        # check the sha1 data is available
671
        vf = self.get_file()
672
        # a simple file
673
        vf.add_lines('a', [], ['a\n'])
674
        # the same file, different metadata
675
        vf.add_lines('b', ['a'], ['a\n'])
676
        # a file differing only in last newline.
677
        vf.add_lines('c', [], ['a'])
678
        self.assertEqual(
679
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('a'))
680
        self.assertEqual(
681
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('b'))
682
        self.assertEqual(
683
            '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', vf.get_sha1('c'))
1594.2.9 by Robert Collins
Teach Knit repositories how to handle ghosts without corrupting at all.
684
        
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
685
1563.2.12 by Robert Collins
Checkpointing: created InterObject to factor out common inter object worker code, added InterVersionedFile and tests to allow making join work between any versionedfile.
686
class TestWeave(TestCaseWithTransport, VersionedFileTestMixIn):
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
687
688
    def get_file(self, name='foo'):
1563.2.25 by Robert Collins
Merge in upstream.
689
        return WeaveFile(name, get_transport(self.get_url('.')), create=True)
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
690
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
691
    def get_file_corrupted_text(self):
1563.2.25 by Robert Collins
Merge in upstream.
692
        w = WeaveFile('foo', get_transport(self.get_url('.')), create=True)
1563.2.13 by Robert Collins
InterVersionedFile implemented.
693
        w.add_lines('v1', [], ['hello\n'])
694
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
695
        
696
        # We are going to invasively corrupt the text
697
        # Make sure the internals of weave are the same
698
        self.assertEqual([('{', 0)
699
                        , 'hello\n'
700
                        , ('}', None)
701
                        , ('{', 1)
702
                        , 'there\n'
703
                        , ('}', None)
704
                        ], w._weave)
705
        
706
        self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
707
                        , '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
708
                        ], w._sha1s)
709
        w.check()
710
        
711
        # Corrupted
712
        w._weave[4] = 'There\n'
713
        return w
714
715
    def get_file_corrupted_checksum(self):
716
        w = self.get_file_corrupted_text()
717
        # Corrected
718
        w._weave[4] = 'there\n'
719
        self.assertEqual('hello\nthere\n', w.get_text('v2'))
720
        
721
        #Invalid checksum, first digit changed
722
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
723
        return w
724
1666.1.6 by Robert Collins
Make knit the default format.
725
    def reopen_file(self, name='foo', create=False):
726
        return WeaveFile(name, get_transport(self.get_url('.')), create=create)
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
727
1563.2.25 by Robert Collins
Merge in upstream.
728
    def test_no_implicit_create(self):
729
        self.assertRaises(errors.NoSuchFile,
730
                          WeaveFile,
731
                          'foo',
732
                          get_transport(self.get_url('.')))
733
1594.2.23 by Robert Collins
Test versioned file storage handling of clean/dirty status for accessed versioned files.
734
    def get_factory(self):
735
        return WeaveFile
736
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
737
1563.2.12 by Robert Collins
Checkpointing: created InterObject to factor out common inter object worker code, added InterVersionedFile and tests to allow making join work between any versionedfile.
738
class TestKnit(TestCaseWithTransport, VersionedFileTestMixIn):
1563.2.4 by Robert Collins
First cut at including the knit implementation of versioned_file.
739
740
    def get_file(self, name='foo'):
1563.2.16 by Robert Collins
Change WeaveStore into VersionedFileStore and make its versoined file class parameterisable.
741
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
1563.2.25 by Robert Collins
Merge in upstream.
742
                                 delta=True, create=True)
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
743
1594.2.23 by Robert Collins
Test versioned file storage handling of clean/dirty status for accessed versioned files.
744
    def get_factory(self):
745
        return KnitVersionedFile
746
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
747
    def get_file_corrupted_text(self):
748
        knit = self.get_file()
749
        knit.add_lines('v1', [], ['hello\n'])
750
        knit.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
751
        return knit
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
752
1666.1.6 by Robert Collins
Make knit the default format.
753
    def reopen_file(self, name='foo', create=False):
754
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
755
            delta=True,
756
            create=create)
1563.2.10 by Robert Collins
Change weave store to be a versioned store, using WeaveFiles which maintain integrity without needing explicit 'put' operations.
757
758
    def test_detection(self):
759
        print "TODO for merging: create a corrupted knit."
1563.2.19 by Robert Collins
stub out a check for knits.
760
        knit = self.get_file()
761
        knit.check()
1563.2.12 by Robert Collins
Checkpointing: created InterObject to factor out common inter object worker code, added InterVersionedFile and tests to allow making join work between any versionedfile.
762
1563.2.25 by Robert Collins
Merge in upstream.
763
    def test_no_implicit_create(self):
764
        self.assertRaises(errors.NoSuchFile,
765
                          KnitVersionedFile,
766
                          'foo',
767
                          get_transport(self.get_url('.')))
768
1563.2.12 by Robert Collins
Checkpointing: created InterObject to factor out common inter object worker code, added InterVersionedFile and tests to allow making join work between any versionedfile.
769
770
class InterString(versionedfile.InterVersionedFile):
771
    """An inter-versionedfile optimised code path for strings.
772
773
    This is for use during testing where we use strings as versionedfiles
774
    so that none of the default regsitered interversionedfile classes will
775
    match - which lets us test the match logic.
776
    """
777
778
    @staticmethod
779
    def is_compatible(source, target):
780
        """InterString is compatible with strings-as-versionedfiles."""
781
        return isinstance(source, str) and isinstance(target, str)
782
783
784
# TODO this and the InterRepository core logic should be consolidatable
785
# if we make the registry a separate class though we still need to 
786
# test the behaviour in the active registry to catch failure-to-handle-
787
# stange-objects
788
class TestInterVersionedFile(TestCaseWithTransport):
789
790
    def test_get_default_inter_versionedfile(self):
791
        # test that the InterVersionedFile.get(a, b) probes
792
        # for a class where is_compatible(a, b) returns
793
        # true and returns a default interversionedfile otherwise.
794
        # This also tests that the default registered optimised interversionedfile
795
        # classes do not barf inappropriately when a surprising versionedfile type
796
        # is handed to them.
797
        dummy_a = "VersionedFile 1."
798
        dummy_b = "VersionedFile 2."
799
        self.assertGetsDefaultInterVersionedFile(dummy_a, dummy_b)
800
801
    def assertGetsDefaultInterVersionedFile(self, a, b):
802
        """Asserts that InterVersionedFile.get(a, b) -> the default."""
803
        inter = versionedfile.InterVersionedFile.get(a, b)
804
        self.assertEqual(versionedfile.InterVersionedFile,
805
                         inter.__class__)
806
        self.assertEqual(a, inter.source)
807
        self.assertEqual(b, inter.target)
808
809
    def test_register_inter_versionedfile_class(self):
810
        # test that a optimised code path provider - a
811
        # InterVersionedFile subclass can be registered and unregistered
812
        # and that it is correctly selected when given a versionedfile
813
        # pair that it returns true on for the is_compatible static method
814
        # check
815
        dummy_a = "VersionedFile 1."
816
        dummy_b = "VersionedFile 2."
817
        versionedfile.InterVersionedFile.register_optimiser(InterString)
818
        try:
819
            # we should get the default for something InterString returns False
820
            # to
821
            self.assertFalse(InterString.is_compatible(dummy_a, None))
822
            self.assertGetsDefaultInterVersionedFile(dummy_a, None)
823
            # and we should get an InterString for a pair it 'likes'
824
            self.assertTrue(InterString.is_compatible(dummy_a, dummy_b))
825
            inter = versionedfile.InterVersionedFile.get(dummy_a, dummy_b)
826
            self.assertEqual(InterString, inter.__class__)
827
            self.assertEqual(dummy_a, inter.source)
828
            self.assertEqual(dummy_b, inter.target)
829
        finally:
830
            versionedfile.InterVersionedFile.unregister_optimiser(InterString)
831
        # now we should get the default InterVersionedFile object again.
832
        self.assertGetsDefaultInterVersionedFile(dummy_a, dummy_b)
1666.1.1 by Robert Collins
Add trivial http-using test for versioned files.
833
834
835
class TestReadonlyHttpMixin(object):
836
837
    def test_readonly_http_works(self):
838
        # we should be able to read from http with a versioned file.
839
        vf = self.get_file()
1666.1.6 by Robert Collins
Make knit the default format.
840
        # try an empty file access
841
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
842
        self.assertEqual([], readonly_vf.versions())
843
        # now with feeling.
1666.1.1 by Robert Collins
Add trivial http-using test for versioned files.
844
        vf.add_lines('1', [], ['a\n'])
845
        vf.add_lines('2', ['1'], ['b\n', 'a\n'])
846
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
1666.1.6 by Robert Collins
Make knit the default format.
847
        self.assertEqual(['1', '2'], vf.versions())
1666.1.1 by Robert Collins
Add trivial http-using test for versioned files.
848
        for version in readonly_vf.versions():
849
            readonly_vf.get_lines(version)
850
851
852
class TestWeaveHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
853
854
    def get_file(self):
855
        return WeaveFile('foo', get_transport(self.get_url('.')), create=True)
856
857
    def get_factory(self):
858
        return WeaveFile
859
860
861
class TestKnitHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
862
863
    def get_file(self):
864
        return KnitVersionedFile('foo', get_transport(self.get_url('.')),
865
                                 delta=True, create=True)
866
867
    def get_factory(self):
868
        return KnitVersionedFile
1664.2.9 by Aaron Bentley
Ported weave merge test to versionedfile
869
870
871
class MergeCasesMixin(object):
872
873
    def doMerge(self, base, a, b, mp):
874
        from cStringIO import StringIO
875
        from textwrap import dedent
876
877
        def addcrlf(x):
878
            return x + '\n'
879
        
880
        w = self.get_file()
881
        w.add_lines('text0', [], map(addcrlf, base))
882
        w.add_lines('text1', ['text0'], map(addcrlf, a))
883
        w.add_lines('text2', ['text0'], map(addcrlf, b))
884
885
        self.log_contents(w)
886
887
        self.log('merge plan:')
888
        p = list(w.plan_merge('text1', 'text2'))
889
        for state, line in p:
890
            if line:
891
                self.log('%12s | %s' % (state, line[:-1]))
892
893
        self.log('merge:')
894
        mt = StringIO()
895
        mt.writelines(w.weave_merge(p))
896
        mt.seek(0)
897
        self.log(mt.getvalue())
898
899
        mp = map(addcrlf, mp)
900
        self.assertEqual(mt.readlines(), mp)
901
        
902
        
903
    def testOneInsert(self):
904
        self.doMerge([],
905
                     ['aa'],
906
                     [],
907
                     ['aa'])
908
909
    def testSeparateInserts(self):
910
        self.doMerge(['aaa', 'bbb', 'ccc'],
911
                     ['aaa', 'xxx', 'bbb', 'ccc'],
912
                     ['aaa', 'bbb', 'yyy', 'ccc'],
913
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
914
915
    def testSameInsert(self):
916
        self.doMerge(['aaa', 'bbb', 'ccc'],
917
                     ['aaa', 'xxx', 'bbb', 'ccc'],
918
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
919
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
920
    overlappedInsertExpected = ['aaa', 'xxx', 'yyy', 'bbb']
921
    def testOverlappedInsert(self):
922
        self.doMerge(['aaa', 'bbb'],
923
                     ['aaa', 'xxx', 'yyy', 'bbb'],
924
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
925
926
        # really it ought to reduce this to 
927
        # ['aaa', 'xxx', 'yyy', 'bbb']
928
929
930
    def testClashReplace(self):
931
        self.doMerge(['aaa'],
932
                     ['xxx'],
933
                     ['yyy', 'zzz'],
934
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
935
                      '>>>>>>> '])
936
937
    def testNonClashInsert1(self):
938
        self.doMerge(['aaa'],
939
                     ['xxx', 'aaa'],
940
                     ['yyy', 'zzz'],
941
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
942
                      '>>>>>>> '])
943
944
    def testNonClashInsert2(self):
945
        self.doMerge(['aaa'],
946
                     ['aaa'],
947
                     ['yyy', 'zzz'],
948
                     ['yyy', 'zzz'])
949
950
951
    def testDeleteAndModify(self):
952
        """Clashing delete and modification.
953
954
        If one side modifies a region and the other deletes it then
955
        there should be a conflict with one side blank.
956
        """
957
958
        #######################################
959
        # skippd, not working yet
960
        return
961
        
962
        self.doMerge(['aaa', 'bbb', 'ccc'],
963
                     ['aaa', 'ddd', 'ccc'],
964
                     ['aaa', 'ccc'],
965
                     ['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
966
967
    def _test_merge_from_strings(self, base, a, b, expected):
968
        w = self.get_file()
969
        w.add_lines('text0', [], base.splitlines(True))
970
        w.add_lines('text1', ['text0'], a.splitlines(True))
971
        w.add_lines('text2', ['text0'], b.splitlines(True))
972
        self.log('merge plan:')
973
        p = list(w.plan_merge('text1', 'text2'))
974
        for state, line in p:
975
            if line:
976
                self.log('%12s | %s' % (state, line[:-1]))
977
        self.log('merge result:')
978
        result_text = ''.join(w.weave_merge(p))
979
        self.log(result_text)
980
        self.assertEqualDiff(result_text, expected)
981
982
    def test_weave_merge_conflicts(self):
983
        # does weave merge properly handle plans that end with unchanged?
984
        result = ''.join(self.get_file().weave_merge([('new-a', 'hello\n')]))
985
        self.assertEqual(result, 'hello\n')
986
987
    def test_deletion_extended(self):
988
        """One side deletes, the other deletes more.
989
        """
990
        base = """\
991
            line 1
992
            line 2
993
            line 3
994
            """
995
        a = """\
996
            line 1
997
            line 2
998
            """
999
        b = """\
1000
            line 1
1001
            """
1002
        result = """\
1003
            line 1
1004
            """
1005
        self._test_merge_from_strings(base, a, b, result)
1006
1007
    def test_deletion_overlap(self):
1008
        """Delete overlapping regions with no other conflict.
1009
1010
        Arguably it'd be better to treat these as agreement, rather than 
1011
        conflict, but for now conflict is safer.
1012
        """
1013
        base = """\
1014
            start context
1015
            int a() {}
1016
            int b() {}
1017
            int c() {}
1018
            end context
1019
            """
1020
        a = """\
1021
            start context
1022
            int a() {}
1023
            end context
1024
            """
1025
        b = """\
1026
            start context
1027
            int c() {}
1028
            end context
1029
            """
1030
        result = """\
1031
            start context
1032
<<<<<<< 
1033
            int a() {}
1034
=======
1035
            int c() {}
1036
>>>>>>> 
1037
            end context
1038
            """
1039
        self._test_merge_from_strings(base, a, b, result)
1040
1041
    def test_agreement_deletion(self):
1042
        """Agree to delete some lines, without conflicts."""
1043
        base = """\
1044
            start context
1045
            base line 1
1046
            base line 2
1047
            end context
1048
            """
1049
        a = """\
1050
            start context
1051
            base line 1
1052
            end context
1053
            """
1054
        b = """\
1055
            start context
1056
            base line 1
1057
            end context
1058
            """
1059
        result = """\
1060
            start context
1061
            base line 1
1062
            end context
1063
            """
1064
        self._test_merge_from_strings(base, a, b, result)
1065
1066
    def test_sync_on_deletion(self):
1067
        """Specific case of merge where we can synchronize incorrectly.
1068
        
1069
        A previous version of the weave merge concluded that the two versions
1070
        agreed on deleting line 2, and this could be a synchronization point.
1071
        Line 1 was then considered in isolation, and thought to be deleted on 
1072
        both sides.
1073
1074
        It's better to consider the whole thing as a disagreement region.
1075
        """
1076
        base = """\
1077
            start context
1078
            base line 1
1079
            base line 2
1080
            end context
1081
            """
1082
        a = """\
1083
            start context
1084
            base line 1
1085
            a's replacement line 2
1086
            end context
1087
            """
1088
        b = """\
1089
            start context
1090
            b replaces
1091
            both lines
1092
            end context
1093
            """
1094
        result = """\
1095
            start context
1096
<<<<<<< 
1097
            base line 1
1098
            a's replacement line 2
1099
=======
1100
            b replaces
1101
            both lines
1102
>>>>>>> 
1103
            end context
1104
            """
1105
        self._test_merge_from_strings(base, a, b, result)
1106
1107
1108
class TestKnitMerge(TestCaseWithTransport, MergeCasesMixin):
1109
1110
    def get_file(self, name='foo'):
1111
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
1112
                                 delta=True, create=True)
1113
1114
    def log_contents(self, w):
1115
        pass
1116
1117
1118
class TestWeaveMerge(TestCaseWithTransport, MergeCasesMixin):
1119
1120
    def get_file(self, name='foo'):
1121
        return WeaveFile(name, get_transport(self.get_url('.')), create=True)
1122
1123
    def log_contents(self, w):
1124
        self.log('weave is:')
1125
        tmpf = StringIO()
1126
        write_weave(w, tmpf)
1127
        self.log(tmpf.getvalue())
1128
1129
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
1130
                                'xxx', '>>>>>>> ', 'bbb']