/brz/remove-bazaar

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