/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
3735.31.2 by John Arbash Meinel
Cleanup trailing whitespace, get test_source to pass by removing asserts.
1
# Copyright (C) 2008, 2009 Canonical Ltd
2
#
0.17.1 by Robert Collins
Starting point. Interface tests hooked up and failing.
3
# This program is free software; you can redistribute it and/or modify
3735.31.2 by John Arbash Meinel
Cleanup trailing whitespace, get test_source to pass by removing asserts.
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
0.17.1 by Robert Collins
Starting point. Interface tests hooked up and failing.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
3735.31.2 by John Arbash Meinel
Cleanup trailing whitespace, get test_source to pass by removing asserts.
12
#
0.17.1 by Robert Collins
Starting point. Interface tests hooked up and failing.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
3735.36.3 by John Arbash Meinel
Add the new address for FSF to the new files.
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
0.17.1 by Robert Collins
Starting point. Interface tests hooked up and failing.
16
17
"""Tests for group compression."""
18
19
import zlib
20
3735.31.1 by John Arbash Meinel
Bring the groupcompress plugin into the brisbane-core branch.
21
from bzrlib import (
4343.3.20 by John Arbash Meinel
Copy the track_external_parent_refs tests over to GCVF.
22
    btree_index,
3735.31.1 by John Arbash Meinel
Bring the groupcompress plugin into the brisbane-core branch.
23
    groupcompress,
3735.32.8 by John Arbash Meinel
Some tests for the LazyGroupCompressFactory
24
    errors,
4343.3.20 by John Arbash Meinel
Copy the track_external_parent_refs tests over to GCVF.
25
    index as _mod_index,
3735.32.7 by John Arbash Meinel
Implement partial decompression support.
26
    osutils,
3735.31.1 by John Arbash Meinel
Bring the groupcompress plugin into the brisbane-core branch.
27
    tests,
4465.2.3 by Aaron Bentley
Update to change redundant inserts into a warning.
28
    trace,
3735.32.20 by John Arbash Meinel
groupcompress now copies the blocks exactly as they were given.
29
    versionedfile,
3735.31.1 by John Arbash Meinel
Bring the groupcompress plugin into the brisbane-core branch.
30
    )
0.23.58 by John Arbash Meinel
fix up the failing tests.
31
from bzrlib.osutils import sha_string
3735.40.5 by John Arbash Meinel
Start adding permutation tests for _groupcompress_py and _groupcompress_pyx
32
from bzrlib.tests.test__groupcompress import CompiledGroupCompressFeature
3735.31.1 by John Arbash Meinel
Bring the groupcompress plugin into the brisbane-core branch.
33
34
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
35
def load_tests(standard_tests, module, loader):
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
36
    """Parameterize tests for all versions of groupcompress."""
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
37
    to_adapt, result = tests.split_suite_by_condition(
38
        standard_tests, tests.condition_isinstance(TestAllGroupCompressors))
39
    scenarios = [
40
        ('python', {'compressor': groupcompress.PythonGroupCompressor}),
41
        ]
3735.40.5 by John Arbash Meinel
Start adding permutation tests for _groupcompress_py and _groupcompress_pyx
42
    if CompiledGroupCompressFeature.available():
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
43
        scenarios.append(('C',
44
            {'compressor': groupcompress.PyrexGroupCompressor}))
3735.40.5 by John Arbash Meinel
Start adding permutation tests for _groupcompress_py and _groupcompress_pyx
45
    return tests.multiply_tests(to_adapt, scenarios, result)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
46
47
0.25.2 by John Arbash Meinel
First cut at meta-info as text form.
48
class TestGroupCompressor(tests.TestCase):
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
49
50
    def _chunks_to_repr_lines(self, chunks):
51
        return '\n'.join(map(repr, ''.join(chunks).split('\n')))
52
53
    def assertEqualDiffEncoded(self, expected, actual):
54
        """Compare the actual content to the expected content.
55
56
        :param expected: A group of chunks that we expect to see
57
        :param actual: The measured 'chunks'
58
59
        We will transform the chunks back into lines, and then run 'repr()'
60
        over them to handle non-ascii characters.
61
        """
62
        self.assertEqualDiff(self._chunks_to_repr_lines(expected),
63
                             self._chunks_to_repr_lines(actual))
64
65
66
class TestAllGroupCompressors(TestGroupCompressor):
0.17.2 by Robert Collins
Core proof of concept working.
67
    """Tests for GroupCompressor"""
68
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
69
    compressor = None # Set by multiply_tests
70
0.17.2 by Robert Collins
Core proof of concept working.
71
    def test_empty_delta(self):
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
72
        compressor = self.compressor()
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
73
        self.assertEqual([], compressor.chunks)
0.17.2 by Robert Collins
Core proof of concept working.
74
75
    def test_one_nosha_delta(self):
76
        # diff against NUKK
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
77
        compressor = self.compressor()
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
78
        sha1, start_point, end_point, _ = compressor.compress(('label',),
0.23.58 by John Arbash Meinel
fix up the failing tests.
79
            'strange\ncommon\n', None)
80
        self.assertEqual(sha_string('strange\ncommon\n'), sha1)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
81
        expected_lines = 'f' '\x0f' 'strange\ncommon\n'
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
82
        self.assertEqual(expected_lines, ''.join(compressor.chunks))
3735.2.162 by John Arbash Meinel
Change GroupCompressor.compress() to return the start_point.
83
        self.assertEqual(0, start_point)
0.17.2 by Robert Collins
Core proof of concept working.
84
        self.assertEqual(sum(map(len, expected_lines)), end_point)
85
3735.2.162 by John Arbash Meinel
Change GroupCompressor.compress() to return the start_point.
86
    def test_empty_content(self):
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
87
        compressor = self.compressor()
3735.2.162 by John Arbash Meinel
Change GroupCompressor.compress() to return the start_point.
88
        # Adding empty bytes should return the 'null' record
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
89
        sha1, start_point, end_point, kind = compressor.compress(('empty',),
90
                                                                 '', None)
3735.2.162 by John Arbash Meinel
Change GroupCompressor.compress() to return the start_point.
91
        self.assertEqual(0, start_point)
92
        self.assertEqual(0, end_point)
93
        self.assertEqual('fulltext', kind)
94
        self.assertEqual(groupcompress._null_sha1, sha1)
95
        self.assertEqual(0, compressor.endpoint)
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
96
        self.assertEqual([], compressor.chunks)
3735.2.162 by John Arbash Meinel
Change GroupCompressor.compress() to return the start_point.
97
        # Even after adding some content
98
        compressor.compress(('content',), 'some\nbytes\n', None)
99
        self.assertTrue(compressor.endpoint > 0)
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
100
        sha1, start_point, end_point, kind = compressor.compress(('empty2',),
101
                                                                 '', None)
3735.2.162 by John Arbash Meinel
Change GroupCompressor.compress() to return the start_point.
102
        self.assertEqual(0, start_point)
103
        self.assertEqual(0, end_point)
104
        self.assertEqual('fulltext', kind)
105
        self.assertEqual(groupcompress._null_sha1, sha1)
106
0.17.11 by Robert Collins
Add extraction of just-compressed texts to support converting from knits.
107
    def test_extract_from_compressor(self):
108
        # Knit fetching will try to reconstruct texts locally which results in
109
        # reading something that is in the compressor stream already.
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
110
        compressor = self.compressor()
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
111
        sha1_1, _, _, _ = compressor.compress(('label',),
0.25.6 by John Arbash Meinel
(tests broken) implement the basic ability to have a separate header
112
            'strange\ncommon long line\nthat needs a 16 byte match\n', None)
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
113
        expected_lines = list(compressor.chunks)
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
114
        sha1_2, _, end_point, _ = compressor.compress(('newlabel',),
0.25.6 by John Arbash Meinel
(tests broken) implement the basic ability to have a separate header
115
            'common long line\nthat needs a 16 byte match\ndifferent\n', None)
0.17.11 by Robert Collins
Add extraction of just-compressed texts to support converting from knits.
116
        # get the first out
0.25.8 by John Arbash Meinel
Fix up the tests. Mostly it was just changing things to
117
        self.assertEqual(('strange\ncommon long line\n'
118
                          'that needs a 16 byte match\n', sha1_1),
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
119
                         compressor.extract(('label',)))
0.17.11 by Robert Collins
Add extraction of just-compressed texts to support converting from knits.
120
        # and the second
0.25.6 by John Arbash Meinel
(tests broken) implement the basic ability to have a separate header
121
        self.assertEqual(('common long line\nthat needs a 16 byte match\n'
122
                          'different\n', sha1_2),
123
                         compressor.extract(('newlabel',)))
0.25.2 by John Arbash Meinel
First cut at meta-info as text form.
124
4241.17.2 by John Arbash Meinel
PythonGroupCompressor needs to support pop_last() properly.
125
    def test_pop_last(self):
126
        compressor = self.compressor()
127
        _, _, _, _ = compressor.compress(('key1',),
128
            'some text\nfor the first entry\n', None)
129
        expected_lines = list(compressor.chunks)
130
        _, _, _, _ = compressor.compress(('key2',),
131
            'some text\nfor the second entry\n', None)
132
        compressor.pop_last()
133
        self.assertEqual(expected_lines, compressor.chunks)
134
0.25.2 by John Arbash Meinel
First cut at meta-info as text form.
135
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
136
class TestPyrexGroupCompressor(TestGroupCompressor):
137
3735.40.5 by John Arbash Meinel
Start adding permutation tests for _groupcompress_py and _groupcompress_pyx
138
    _test_needs_features = [CompiledGroupCompressFeature]
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
139
    compressor = groupcompress.PyrexGroupCompressor
140
141
    def test_stats(self):
142
        compressor = self.compressor()
3735.40.7 by John Arbash Meinel
Move even more functionality into EquivalenceTable.
143
        compressor.compress(('label',),
144
                            'strange\n'
145
                            'common very very long line\n'
146
                            'plus more text\n', None)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
147
        compressor.compress(('newlabel',),
3735.40.7 by John Arbash Meinel
Move even more functionality into EquivalenceTable.
148
                            'common very very long line\n'
149
                            'plus more text\n'
150
                            'different\n'
151
                            'moredifferent\n', None)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
152
        compressor.compress(('label3',),
3735.40.7 by John Arbash Meinel
Move even more functionality into EquivalenceTable.
153
                            'new\n'
154
                            'common very very long line\n'
155
                            'plus more text\n'
156
                            'different\n'
157
                            'moredifferent\n', None)
158
        self.assertAlmostEqual(1.9, compressor.ratio(), 1)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
159
160
    def test_two_nosha_delta(self):
161
        compressor = self.compressor()
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
162
        sha1_1, _, _, _ = compressor.compress(('label',),
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
163
            'strange\ncommon long line\nthat needs a 16 byte match\n', None)
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
164
        expected_lines = list(compressor.chunks)
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
165
        sha1_2, start_point, end_point, _ = compressor.compress(('newlabel',),
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
166
            'common long line\nthat needs a 16 byte match\ndifferent\n', None)
167
        self.assertEqual(sha_string('common long line\n'
168
                                    'that needs a 16 byte match\n'
169
                                    'different\n'), sha1_2)
170
        expected_lines.extend([
171
            # 'delta', delta length
3735.40.10 by John Arbash Meinel
Merge in the new delta format code.
172
            'd\x0f',
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
173
            # source and target length
3735.40.10 by John Arbash Meinel
Merge in the new delta format code.
174
            '\x36',
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
175
            # copy the line common
176
            '\x91\x0a\x2c', #copy, offset 0x0a, len 0x2c
177
            # add the line different, and the trailing newline
178
            '\x0adifferent\n', # insert 10 bytes
179
            ])
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
180
        self.assertEqualDiffEncoded(expected_lines, compressor.chunks)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
181
        self.assertEqual(sum(map(len, expected_lines)), end_point)
182
183
    def test_three_nosha_delta(self):
184
        # The first interesting test: make a change that should use lines from
185
        # both parents.
186
        compressor = self.compressor()
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
187
        sha1_1, _, _, _ = compressor.compress(('label',),
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
188
            'strange\ncommon very very long line\nwith some extra text\n', None)
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
189
        sha1_2, _, _, _ = compressor.compress(('newlabel',),
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
190
            'different\nmoredifferent\nand then some more\n', None)
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
191
        expected_lines = list(compressor.chunks)
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
192
        sha1_3, start_point, end_point, _ = compressor.compress(('label3',),
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
193
            'new\ncommon very very long line\nwith some extra text\n'
194
            'different\nmoredifferent\nand then some more\n',
195
            None)
196
        self.assertEqual(
197
            sha_string('new\ncommon very very long line\nwith some extra text\n'
198
                       'different\nmoredifferent\nand then some more\n'),
199
            sha1_3)
200
        expected_lines.extend([
201
            # 'delta', delta length
3735.40.10 by John Arbash Meinel
Merge in the new delta format code.
202
            'd\x0b',
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
203
            # source and target length
3735.40.10 by John Arbash Meinel
Merge in the new delta format code.
204
            '\x5f'
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
205
            # insert new
206
            '\x03new',
207
            # Copy of first parent 'common' range
208
            '\x91\x09\x31' # copy, offset 0x09, 0x31 bytes
209
            # Copy of second parent 'different' range
210
            '\x91\x3c\x2b' # copy, offset 0x3c, 0x2b bytes
211
            ])
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
212
        self.assertEqualDiffEncoded(expected_lines, compressor.chunks)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
213
        self.assertEqual(sum(map(len, expected_lines)), end_point)
214
215
216
class TestPythonGroupCompressor(TestGroupCompressor):
217
218
    compressor = groupcompress.PythonGroupCompressor
219
220
    def test_stats(self):
221
        compressor = self.compressor()
3735.40.7 by John Arbash Meinel
Move even more functionality into EquivalenceTable.
222
        compressor.compress(('label',),
223
                            'strange\n'
224
                            'common very very long line\n'
225
                            'plus more text\n', None)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
226
        compressor.compress(('newlabel',),
3735.40.7 by John Arbash Meinel
Move even more functionality into EquivalenceTable.
227
                            'common very very long line\n'
228
                            'plus more text\n'
229
                            'different\n'
230
                            'moredifferent\n', None)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
231
        compressor.compress(('label3',),
3735.40.7 by John Arbash Meinel
Move even more functionality into EquivalenceTable.
232
                            'new\n'
233
                            'common very very long line\n'
234
                            'plus more text\n'
235
                            'different\n'
236
                            'moredifferent\n', None)
237
        self.assertAlmostEqual(1.9, compressor.ratio(), 1)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
238
239
    def test_two_nosha_delta(self):
240
        compressor = self.compressor()
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
241
        sha1_1, _, _, _ = compressor.compress(('label',),
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
242
            'strange\ncommon long line\nthat needs a 16 byte match\n', None)
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
243
        expected_lines = list(compressor.chunks)
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
244
        sha1_2, start_point, end_point, _ = compressor.compress(('newlabel',),
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
245
            'common long line\nthat needs a 16 byte match\ndifferent\n', None)
246
        self.assertEqual(sha_string('common long line\n'
247
                                    'that needs a 16 byte match\n'
248
                                    'different\n'), sha1_2)
249
        expected_lines.extend([
250
            # 'delta', delta length
3735.40.10 by John Arbash Meinel
Merge in the new delta format code.
251
            'd\x0f',
252
            # target length
253
            '\x36',
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
254
            # copy the line common
255
            '\x91\x0a\x2c', #copy, offset 0x0a, len 0x2c
256
            # add the line different, and the trailing newline
257
            '\x0adifferent\n', # insert 10 bytes
258
            ])
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
259
        self.assertEqualDiffEncoded(expected_lines, compressor.chunks)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
260
        self.assertEqual(sum(map(len, expected_lines)), end_point)
261
262
    def test_three_nosha_delta(self):
263
        # The first interesting test: make a change that should use lines from
264
        # both parents.
265
        compressor = self.compressor()
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
266
        sha1_1, _, _, _ = compressor.compress(('label',),
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
267
            'strange\ncommon very very long line\nwith some extra text\n', None)
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
268
        sha1_2, _, _, _ = compressor.compress(('newlabel',),
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
269
            'different\nmoredifferent\nand then some more\n', None)
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
270
        expected_lines = list(compressor.chunks)
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
271
        sha1_3, start_point, end_point, _ = compressor.compress(('label3',),
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
272
            'new\ncommon very very long line\nwith some extra text\n'
273
            'different\nmoredifferent\nand then some more\n',
274
            None)
275
        self.assertEqual(
276
            sha_string('new\ncommon very very long line\nwith some extra text\n'
277
                       'different\nmoredifferent\nand then some more\n'),
278
            sha1_3)
279
        expected_lines.extend([
280
            # 'delta', delta length
3735.40.10 by John Arbash Meinel
Merge in the new delta format code.
281
            'd\x0c',
282
            # target length
283
            '\x5f'
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
284
            # insert new
285
            '\x04new\n',
286
            # Copy of first parent 'common' range
287
            '\x91\x0a\x30' # copy, offset 0x0a, 0x30 bytes
288
            # Copy of second parent 'different' range
289
            '\x91\x3c\x2b' # copy, offset 0x3c, 0x2b bytes
290
            ])
3735.40.17 by John Arbash Meinel
Change the attribute from 'lines' to 'chunks' to make it more
291
        self.assertEqualDiffEncoded(expected_lines, compressor.chunks)
3735.40.4 by John Arbash Meinel
Factor out tests that rely on the exact bytecode.
292
        self.assertEqual(sum(map(len, expected_lines)), end_point)
293
294
0.25.2 by John Arbash Meinel
First cut at meta-info as text form.
295
class TestGroupCompressBlock(tests.TestCase):
296
3735.32.15 by John Arbash Meinel
Change the GroupCompressBlock code to allow not recording 'end'.
297
    def make_block(self, key_to_text):
298
        """Create a GroupCompressBlock, filling it with the given texts."""
299
        compressor = groupcompress.GroupCompressor()
300
        start = 0
301
        for key in sorted(key_to_text):
302
            compressor.compress(key, key_to_text[key], None)
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
303
        locs = dict((key, (start, end)) for key, (start, _, end, _)
304
                    in compressor.labels_deltas.iteritems())
3735.32.23 by John Arbash Meinel
Add a _LazyGroupContentManager._check_rebuild_block
305
        block = compressor.flush()
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
306
        raw_bytes = block.to_bytes()
3735.32.23 by John Arbash Meinel
Add a _LazyGroupContentManager._check_rebuild_block
307
        # Go through from_bytes(to_bytes()) so that we start with a compressed
308
        # content object
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
309
        return locs, groupcompress.GroupCompressBlock.from_bytes(raw_bytes)
3735.32.15 by John Arbash Meinel
Change the GroupCompressBlock code to allow not recording 'end'.
310
0.25.2 by John Arbash Meinel
First cut at meta-info as text form.
311
    def test_from_empty_bytes(self):
3735.31.1 by John Arbash Meinel
Bring the groupcompress plugin into the brisbane-core branch.
312
        self.assertRaises(ValueError,
0.25.2 by John Arbash Meinel
First cut at meta-info as text form.
313
                          groupcompress.GroupCompressBlock.from_bytes, '')
314
0.25.4 by John Arbash Meinel
We at least have the rudimentary ability to encode and decode values.
315
    def test_from_minimal_bytes(self):
3735.32.4 by John Arbash Meinel
Change the byte representation of a groupcompress block.
316
        block = groupcompress.GroupCompressBlock.from_bytes(
3735.38.4 by John Arbash Meinel
Another disk format change.
317
            'gcb1z\n0\n0\n')
0.25.4 by John Arbash Meinel
We at least have the rudimentary ability to encode and decode values.
318
        self.assertIsInstance(block, groupcompress.GroupCompressBlock)
3735.32.6 by John Arbash Meinel
A bit of reworking changes things so content is expanded at extract() time.
319
        self.assertIs(None, block._content)
320
        self.assertEqual('', block._z_content)
321
        block._ensure_content()
3735.32.5 by John Arbash Meinel
Change the parsing code to start out just holding the compressed bytes.
322
        self.assertEqual('', block._content)
3735.32.27 by John Arbash Meinel
Have _LazyGroupContentManager pre-extract everything it holds.
323
        self.assertEqual('', block._z_content)
3735.32.6 by John Arbash Meinel
A bit of reworking changes things so content is expanded at extract() time.
324
        block._ensure_content() # Ensure content is safe to call 2x
0.25.4 by John Arbash Meinel
We at least have the rudimentary ability to encode and decode values.
325
4241.6.6 by Robert Collins, John Arbash Meinel, Ian Clathworthy, Vincent Ladeuil
Groupcompress from brisbane-core.
326
    def test_from_invalid(self):
327
        self.assertRaises(ValueError,
328
                          groupcompress.GroupCompressBlock.from_bytes,
329
                          'this is not a valid header')
330
3735.38.4 by John Arbash Meinel
Another disk format change.
331
    def test_from_bytes(self):
3735.32.4 by John Arbash Meinel
Change the byte representation of a groupcompress block.
332
        content = ('a tiny bit of content\n')
333
        z_content = zlib.compress(content)
334
        z_bytes = (
335
            'gcb1z\n' # group compress block v1 plain
336
            '%d\n' # Length of compressed content
337
            '%d\n' # Length of uncompressed content
338
            '%s'   # Compressed content
3735.38.4 by John Arbash Meinel
Another disk format change.
339
            ) % (len(z_content), len(content), z_content)
0.25.6 by John Arbash Meinel
(tests broken) implement the basic ability to have a separate header
340
        block = groupcompress.GroupCompressBlock.from_bytes(
3735.32.4 by John Arbash Meinel
Change the byte representation of a groupcompress block.
341
            z_bytes)
3735.32.6 by John Arbash Meinel
A bit of reworking changes things so content is expanded at extract() time.
342
        self.assertEqual(z_content, block._z_content)
343
        self.assertIs(None, block._content)
3735.38.4 by John Arbash Meinel
Another disk format change.
344
        self.assertEqual(len(z_content), block._z_content_length)
345
        self.assertEqual(len(content), block._content_length)
3735.32.10 by John Arbash Meinel
test that we support reading from the gc blocks that didn't have their lengths.
346
        block._ensure_content()
3735.32.27 by John Arbash Meinel
Have _LazyGroupContentManager pre-extract everything it holds.
347
        self.assertEqual(z_content, block._z_content)
3735.32.10 by John Arbash Meinel
test that we support reading from the gc blocks that didn't have their lengths.
348
        self.assertEqual(content, block._content)
349
0.25.2 by John Arbash Meinel
First cut at meta-info as text form.
350
    def test_to_bytes(self):
3735.38.4 by John Arbash Meinel
Another disk format change.
351
        content = ('this is some content\n'
352
                   'this content will be compressed\n')
0.25.2 by John Arbash Meinel
First cut at meta-info as text form.
353
        gcb = groupcompress.GroupCompressBlock()
3735.38.4 by John Arbash Meinel
Another disk format change.
354
        gcb.set_content(content)
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
355
        bytes = gcb.to_bytes()
3735.38.4 by John Arbash Meinel
Another disk format change.
356
        self.assertEqual(gcb._z_content_length, len(gcb._z_content))
357
        self.assertEqual(gcb._content_length, len(content))
3735.32.4 by John Arbash Meinel
Change the byte representation of a groupcompress block.
358
        expected_header =('gcb1z\n' # group compress block v1 zlib
3735.38.4 by John Arbash Meinel
Another disk format change.
359
                          '%d\n' # Length of compressed content
360
                          '%d\n' # Length of uncompressed content
361
                         ) % (gcb._z_content_length, gcb._content_length)
3735.32.4 by John Arbash Meinel
Change the byte representation of a groupcompress block.
362
        self.assertStartsWith(bytes, expected_header)
363
        remaining_bytes = bytes[len(expected_header):]
0.25.5 by John Arbash Meinel
Now using a zlib compressed format.
364
        raw_bytes = zlib.decompress(remaining_bytes)
3735.38.4 by John Arbash Meinel
Another disk format change.
365
        self.assertEqual(content, raw_bytes)
3735.32.3 by John Arbash Meinel
Start doing some direct GCVF tests.
366
3735.32.7 by John Arbash Meinel
Implement partial decompression support.
367
    def test_partial_decomp(self):
368
        content_chunks = []
369
        # We need a sufficient amount of data so that zlib.decompress has
370
        # partial decompression to work with. Most auto-generated data
371
        # compresses a bit too well, we want a combination, so we combine a sha
372
        # hash with compressible data.
373
        for i in xrange(2048):
374
            next_content = '%d\nThis is a bit of duplicate text\n' % (i,)
375
            content_chunks.append(next_content)
376
            next_sha1 = osutils.sha_string(next_content)
377
            content_chunks.append(next_sha1 + '\n')
378
        content = ''.join(content_chunks)
379
        self.assertEqual(158634, len(content))
380
        z_content = zlib.compress(content)
381
        self.assertEqual(57182, len(z_content))
382
        block = groupcompress.GroupCompressBlock()
383
        block._z_content = z_content
384
        block._z_content_length = len(z_content)
3735.32.8 by John Arbash Meinel
Some tests for the LazyGroupCompressFactory
385
        block._compressor_name = 'zlib'
3735.32.7 by John Arbash Meinel
Implement partial decompression support.
386
        block._content_length = 158634
387
        self.assertIs(None, block._content)
388
        block._ensure_content(100)
389
        self.assertIsNot(None, block._content)
390
        # We have decompressed at least 100 bytes
391
        self.assertTrue(len(block._content) >= 100)
392
        # We have not decompressed the whole content
393
        self.assertTrue(len(block._content) < 158634)
394
        self.assertEqualDiff(content[:len(block._content)], block._content)
395
        # ensuring content that we already have shouldn't cause any more data
396
        # to be extracted
397
        cur_len = len(block._content)
398
        block._ensure_content(cur_len - 10)
399
        self.assertEqual(cur_len, len(block._content))
400
        # Now we want a bit more content
401
        cur_len += 10
402
        block._ensure_content(cur_len)
403
        self.assertTrue(len(block._content) >= cur_len)
404
        self.assertTrue(len(block._content) < 158634)
405
        self.assertEqualDiff(content[:len(block._content)], block._content)
406
        # And now lets finish
407
        block._ensure_content(158634)
408
        self.assertEqualDiff(content, block._content)
3735.32.8 by John Arbash Meinel
Some tests for the LazyGroupCompressFactory
409
        # And the decompressor is finalized
3735.32.7 by John Arbash Meinel
Implement partial decompression support.
410
        self.assertIs(None, block._z_content_decompressor)
411
3735.32.11 by John Arbash Meinel
Add tests for the ability to do partial decompression without knowing the final length.
412
    def test_partial_decomp_no_known_length(self):
413
        content_chunks = []
414
        for i in xrange(2048):
415
            next_content = '%d\nThis is a bit of duplicate text\n' % (i,)
416
            content_chunks.append(next_content)
417
            next_sha1 = osutils.sha_string(next_content)
418
            content_chunks.append(next_sha1 + '\n')
419
        content = ''.join(content_chunks)
420
        self.assertEqual(158634, len(content))
421
        z_content = zlib.compress(content)
422
        self.assertEqual(57182, len(z_content))
423
        block = groupcompress.GroupCompressBlock()
424
        block._z_content = z_content
425
        block._z_content_length = len(z_content)
426
        block._compressor_name = 'zlib'
427
        block._content_length = None # Don't tell the decompressed length
428
        self.assertIs(None, block._content)
429
        block._ensure_content(100)
430
        self.assertIsNot(None, block._content)
431
        # We have decompressed at least 100 bytes
432
        self.assertTrue(len(block._content) >= 100)
433
        # We have not decompressed the whole content
434
        self.assertTrue(len(block._content) < 158634)
435
        self.assertEqualDiff(content[:len(block._content)], block._content)
436
        # ensuring content that we already have shouldn't cause any more data
437
        # to be extracted
438
        cur_len = len(block._content)
439
        block._ensure_content(cur_len - 10)
440
        self.assertEqual(cur_len, len(block._content))
441
        # Now we want a bit more content
442
        cur_len += 10
443
        block._ensure_content(cur_len)
444
        self.assertTrue(len(block._content) >= cur_len)
445
        self.assertTrue(len(block._content) < 158634)
446
        self.assertEqualDiff(content[:len(block._content)], block._content)
447
        # And now lets finish
448
        block._ensure_content()
449
        self.assertEqualDiff(content, block._content)
450
        # And the decompressor is finalized
451
        self.assertIs(None, block._z_content_decompressor)
452
4300.1.1 by John Arbash Meinel
Add the ability to convert a gc block into 'human readable' form.
453
    def test__dump(self):
454
        dup_content = 'some duplicate content\nwhich is sufficiently long\n'
455
        key_to_text = {('1',): dup_content + '1 unique\n',
456
                       ('2',): dup_content + '2 extra special\n'}
457
        locs, block = self.make_block(key_to_text)
458
        self.assertEqual([('f', len(key_to_text[('1',)])),
459
                          ('d', 21, len(key_to_text[('2',)]),
460
                           [('c', 2, len(dup_content)),
461
                            ('i', len('2 extra special\n'), '')
462
                           ]),
463
                         ], block._dump())
464
3735.32.3 by John Arbash Meinel
Start doing some direct GCVF tests.
465
466
class TestCaseWithGroupCompressVersionedFiles(tests.TestCaseWithTransport):
467
3735.32.20 by John Arbash Meinel
groupcompress now copies the blocks exactly as they were given.
468
    def make_test_vf(self, create_graph, keylength=1, do_cleanup=True,
4465.2.4 by Aaron Bentley
Switch between warn and raise depending on inconsistent_fatal.
469
                     dir='.', inconsistency_fatal=True):
3735.32.20 by John Arbash Meinel
groupcompress now copies the blocks exactly as they were given.
470
        t = self.get_transport(dir)
471
        t.ensure_base()
3735.32.3 by John Arbash Meinel
Start doing some direct GCVF tests.
472
        vf = groupcompress.make_pack_factory(graph=create_graph,
4465.2.4 by Aaron Bentley
Switch between warn and raise depending on inconsistent_fatal.
473
            delta=False, keylength=keylength,
474
            inconsistency_fatal=inconsistency_fatal)(t)
3735.32.3 by John Arbash Meinel
Start doing some direct GCVF tests.
475
        if do_cleanup:
476
            self.addCleanup(groupcompress.cleanup_pack_group, vf)
477
        return vf
478
3735.32.8 by John Arbash Meinel
Some tests for the LazyGroupCompressFactory
479
480
class TestGroupCompressVersionedFiles(TestCaseWithGroupCompressVersionedFiles):
481
4343.3.20 by John Arbash Meinel
Copy the track_external_parent_refs tests over to GCVF.
482
    def make_g_index(self, name, ref_lists=0, nodes=[]):
483
        builder = btree_index.BTreeBuilder(ref_lists)
484
        for node, references, value in nodes:
485
            builder.add_node(node, references, value)
486
        stream = builder.finish()
487
        trans = self.get_transport()
488
        size = trans.put_file(name, stream)
489
        return btree_index.BTreeGraphIndex(trans, name, size)
490
491
    def make_g_index_missing_parent(self):
492
        graph_index = self.make_g_index('missing_parent', 1,
493
            [(('parent', ), '2 78 2 10', ([],)),
494
             (('tip', ), '2 78 2 10',
495
              ([('parent', ), ('missing-parent', )],)),
496
              ])
497
        return graph_index
498
3735.32.3 by John Arbash Meinel
Start doing some direct GCVF tests.
499
    def test_get_record_stream_as_requested(self):
500
        # Consider promoting 'as-requested' to general availability, and
501
        # make this a VF interface test
3735.32.21 by John Arbash Meinel
We now have a 'reuse_blocks=False' flag for autopack et al.
502
        vf = self.make_test_vf(False, dir='source')
3735.32.3 by John Arbash Meinel
Start doing some direct GCVF tests.
503
        vf.add_lines(('a',), (), ['lines\n'])
504
        vf.add_lines(('b',), (), ['lines\n'])
505
        vf.add_lines(('c',), (), ['lines\n'])
506
        vf.add_lines(('d',), (), ['lines\n'])
507
        vf.writer.end()
508
        keys = [record.key for record in vf.get_record_stream(
509
                    [('a',), ('b',), ('c',), ('d',)],
510
                    'as-requested', False)]
511
        self.assertEqual([('a',), ('b',), ('c',), ('d',)], keys)
512
        keys = [record.key for record in vf.get_record_stream(
513
                    [('b',), ('a',), ('d',), ('c',)],
514
                    'as-requested', False)]
515
        self.assertEqual([('b',), ('a',), ('d',), ('c',)], keys)
516
517
        # It should work even after being repacked into another VF
3735.32.20 by John Arbash Meinel
groupcompress now copies the blocks exactly as they were given.
518
        vf2 = self.make_test_vf(False, dir='target')
3735.32.3 by John Arbash Meinel
Start doing some direct GCVF tests.
519
        vf2.insert_record_stream(vf.get_record_stream(
520
                    [('b',), ('a',), ('d',), ('c',)], 'as-requested', False))
521
        vf2.writer.end()
522
523
        keys = [record.key for record in vf2.get_record_stream(
524
                    [('a',), ('b',), ('c',), ('d',)],
525
                    'as-requested', False)]
526
        self.assertEqual([('a',), ('b',), ('c',), ('d',)], keys)
527
        keys = [record.key for record in vf2.get_record_stream(
528
                    [('b',), ('a',), ('d',), ('c',)],
529
                    'as-requested', False)]
530
        self.assertEqual([('b',), ('a',), ('d',), ('c',)], keys)
3735.32.8 by John Arbash Meinel
Some tests for the LazyGroupCompressFactory
531
3735.32.21 by John Arbash Meinel
We now have a 'reuse_blocks=False' flag for autopack et al.
532
    def test_insert_record_stream_re_uses_blocks(self):
533
        vf = self.make_test_vf(True, dir='source')
3735.32.20 by John Arbash Meinel
groupcompress now copies the blocks exactly as they were given.
534
        def grouped_stream(revision_ids, first_parents=()):
535
            parents = first_parents
536
            for revision_id in revision_ids:
537
                key = (revision_id,)
538
                record = versionedfile.FulltextContentFactory(
539
                    key, parents, None,
540
                    'some content that is\n'
541
                    'identical except for\n'
542
                    'revision_id:%s\n' % (revision_id,))
543
                yield record
544
                parents = (key,)
545
        # One group, a-d
546
        vf.insert_record_stream(grouped_stream(['a', 'b', 'c', 'd']))
547
        # Second group, e-h
548
        vf.insert_record_stream(grouped_stream(['e', 'f', 'g', 'h'],
549
                                               first_parents=(('d',),)))
550
        block_bytes = {}
551
        stream = vf.get_record_stream([(r,) for r in 'abcdefgh'],
552
                                      'unordered', False)
3735.32.21 by John Arbash Meinel
We now have a 'reuse_blocks=False' flag for autopack et al.
553
        num_records = 0
3735.32.20 by John Arbash Meinel
groupcompress now copies the blocks exactly as they were given.
554
        for record in stream:
555
            if record.key in [('a',), ('e',)]:
556
                self.assertEqual('groupcompress-block', record.storage_kind)
557
            else:
558
                self.assertEqual('groupcompress-block-ref',
559
                                 record.storage_kind)
560
            block_bytes[record.key] = record._manager._block._z_content
3735.32.21 by John Arbash Meinel
We now have a 'reuse_blocks=False' flag for autopack et al.
561
            num_records += 1
562
        self.assertEqual(8, num_records)
3735.32.20 by John Arbash Meinel
groupcompress now copies the blocks exactly as they were given.
563
        for r in 'abcd':
564
            key = (r,)
565
            self.assertIs(block_bytes[key], block_bytes[('a',)])
566
            self.assertNotEqual(block_bytes[key], block_bytes[('e',)])
567
        for r in 'efgh':
568
            key = (r,)
569
            self.assertIs(block_bytes[key], block_bytes[('e',)])
570
            self.assertNotEqual(block_bytes[key], block_bytes[('a',)])
571
        # Now copy the blocks into another vf, and ensure that the blocks are
572
        # preserved without creating new entries
573
        vf2 = self.make_test_vf(True, dir='target')
574
        # ordering in 'groupcompress' order, should actually swap the groups in
575
        # the target vf, but the groups themselves should not be disturbed.
576
        vf2.insert_record_stream(vf.get_record_stream(
577
            [(r,) for r in 'abcdefgh'], 'groupcompress', False))
578
        stream = vf2.get_record_stream([(r,) for r in 'abcdefgh'],
579
                                       'groupcompress', False)
580
        vf2.writer.end()
3735.32.21 by John Arbash Meinel
We now have a 'reuse_blocks=False' flag for autopack et al.
581
        num_records = 0
3735.32.20 by John Arbash Meinel
groupcompress now copies the blocks exactly as they were given.
582
        for record in stream:
3735.32.21 by John Arbash Meinel
We now have a 'reuse_blocks=False' flag for autopack et al.
583
            num_records += 1
3735.32.20 by John Arbash Meinel
groupcompress now copies the blocks exactly as they were given.
584
            self.assertEqual(block_bytes[record.key],
585
                             record._manager._block._z_content)
3735.32.21 by John Arbash Meinel
We now have a 'reuse_blocks=False' flag for autopack et al.
586
        self.assertEqual(8, num_records)
587
588
    def test__insert_record_stream_no_reuse_block(self):
589
        vf = self.make_test_vf(True, dir='source')
590
        def grouped_stream(revision_ids, first_parents=()):
591
            parents = first_parents
592
            for revision_id in revision_ids:
593
                key = (revision_id,)
594
                record = versionedfile.FulltextContentFactory(
595
                    key, parents, None,
596
                    'some content that is\n'
597
                    'identical except for\n'
598
                    'revision_id:%s\n' % (revision_id,))
599
                yield record
600
                parents = (key,)
601
        # One group, a-d
602
        vf.insert_record_stream(grouped_stream(['a', 'b', 'c', 'd']))
603
        # Second group, e-h
604
        vf.insert_record_stream(grouped_stream(['e', 'f', 'g', 'h'],
605
                                               first_parents=(('d',),)))
606
        vf.writer.end()
607
        self.assertEqual(8, len(list(vf.get_record_stream(
608
                                        [(r,) for r in 'abcdefgh'],
609
                                        'unordered', False))))
610
        # Now copy the blocks into another vf, and ensure that the blocks are
611
        # preserved without creating new entries
612
        vf2 = self.make_test_vf(True, dir='target')
613
        # ordering in 'groupcompress' order, should actually swap the groups in
614
        # the target vf, but the groups themselves should not be disturbed.
615
        list(vf2._insert_record_stream(vf.get_record_stream(
616
            [(r,) for r in 'abcdefgh'], 'groupcompress', False),
617
            reuse_blocks=False))
618
        vf2.writer.end()
619
        # After inserting with reuse_blocks=False, we should have everything in
620
        # a single new block.
621
        stream = vf2.get_record_stream([(r,) for r in 'abcdefgh'],
622
                                       'groupcompress', False)
623
        block = None
624
        for record in stream:
625
            if block is None:
626
                block = record._manager._block
627
            else:
628
                self.assertIs(block, record._manager._block)
629
4343.3.20 by John Arbash Meinel
Copy the track_external_parent_refs tests over to GCVF.
630
    def test_add_missing_noncompression_parent_unvalidated_index(self):
631
        unvalidated = self.make_g_index_missing_parent()
632
        combined = _mod_index.CombinedGraphIndex([unvalidated])
633
        index = groupcompress._GCGraphIndex(combined,
4343.3.21 by John Arbash Meinel
Implement get_missing_parents in terms of _KeyRefs.
634
            is_locked=lambda: True, parents=True,
635
            track_external_parent_refs=True)
4343.3.20 by John Arbash Meinel
Copy the track_external_parent_refs tests over to GCVF.
636
        index.scan_unvalidated_index(unvalidated)
637
        self.assertEqual(
638
            frozenset([('missing-parent',)]), index.get_missing_parents())
639
640
    def test_track_external_parent_refs(self):
641
        g_index = self.make_g_index('empty', 1, [])
642
        mod_index = btree_index.BTreeBuilder(1, 1)
643
        combined = _mod_index.CombinedGraphIndex([g_index, mod_index])
644
        index = groupcompress._GCGraphIndex(combined,
645
            is_locked=lambda: True, parents=True,
4343.3.21 by John Arbash Meinel
Implement get_missing_parents in terms of _KeyRefs.
646
            add_callback=mod_index.add_nodes,
647
            track_external_parent_refs=True)
4343.3.20 by John Arbash Meinel
Copy the track_external_parent_refs tests over to GCVF.
648
        index.add_records([
649
            (('new-key',), '2 10 2 10', [(('parent-1',), ('parent-2',))])])
650
        self.assertEqual(
651
            frozenset([('parent-1',), ('parent-2',)]),
652
            index.get_missing_parents())
653
4465.2.3 by Aaron Bentley
Update to change redundant inserts into a warning.
654
    def make_source_with_b(self, a_parent, path):
655
        source = self.make_test_vf(True, dir=path)
656
        source.add_lines(('a',), (), ['lines\n'])
657
        if a_parent:
658
            b_parents = (('a',),)
659
        else:
660
            b_parents = ()
661
        source.add_lines(('b',), b_parents, ['lines\n'])
662
        return source
663
4465.2.4 by Aaron Bentley
Switch between warn and raise depending on inconsistent_fatal.
664
    def do_inconsistent_inserts(self, inconsistency_fatal):
665
        target = self.make_test_vf(True, dir='target',
666
                                   inconsistency_fatal=inconsistency_fatal)
667
        for x in range(2):
668
            source = self.make_source_with_b(x==1, 'source%s' % x)
669
            target.insert_record_stream(source.get_record_stream(
670
                [('b',)], 'unordered', False))
671
4465.2.3 by Aaron Bentley
Update to change redundant inserts into a warning.
672
    def test_inconsistent_redundant_inserts_warn(self):
4465.2.2 by Aaron Bentley
Add test that duplicates are skipped.
673
        """Should not insert a record that is already present."""
4465.2.3 by Aaron Bentley
Update to change redundant inserts into a warning.
674
        warnings = []
675
        def warning(template, args):
676
            warnings.append(template % args)
677
        _trace_warning = trace.warning
678
        trace.warning = warning
679
        try:
4465.2.4 by Aaron Bentley
Switch between warn and raise depending on inconsistent_fatal.
680
            self.do_inconsistent_inserts(inconsistency_fatal=False)
4465.2.3 by Aaron Bentley
Update to change redundant inserts into a warning.
681
        finally:
682
            trace.warning = _trace_warning
683
        self.assertEqual(["inconsistent details in skipped record: ('b',)"
684
                          " ('42 32 0 8', ((),)) ('74 32 0 8', ((('a',),),))"],
685
                         warnings)
3735.32.8 by John Arbash Meinel
Some tests for the LazyGroupCompressFactory
686
4465.2.4 by Aaron Bentley
Switch between warn and raise depending on inconsistent_fatal.
687
    def test_inconsistent_redundant_inserts_raises(self):
688
        e = self.assertRaises(errors.KnitCorrupt, self.do_inconsistent_inserts,
689
                              inconsistency_fatal=True)
690
        self.assertContainsRe(str(e), "Knit.* corrupt: inconsistent details"
691
                              " in add_records:"
692
                              " \('b',\) \('42 32 0 8', \(\(\),\)\) \('74 32"
693
                              " 0 8', \(\(\('a',\),\),\)\)")
694
695
3735.32.14 by John Arbash Meinel
Move the tests over to testing the LazyGroupContentManager object.
696
class TestLazyGroupCompress(tests.TestCaseWithTransport):
3735.32.8 by John Arbash Meinel
Some tests for the LazyGroupCompressFactory
697
3735.32.14 by John Arbash Meinel
Move the tests over to testing the LazyGroupContentManager object.
698
    _texts = {
699
        ('key1',): "this is a text\n"
700
                   "with a reasonable amount of compressible bytes\n",
701
        ('key2',): "another text\n"
702
                   "with a reasonable amount of compressible bytes\n",
3735.32.15 by John Arbash Meinel
Change the GroupCompressBlock code to allow not recording 'end'.
703
        ('key3',): "yet another text which won't be extracted\n"
704
                   "with a reasonable amount of compressible bytes\n",
705
        ('key4',): "this will be extracted\n"
3735.38.2 by John Arbash Meinel
Make the text for key4 slightly longer, rather than include key3.
706
                   "but references most of its bytes from\n"
3735.32.15 by John Arbash Meinel
Change the GroupCompressBlock code to allow not recording 'end'.
707
                   "yet another text which won't be extracted\n"
708
                   "with a reasonable amount of compressible bytes\n",
3735.32.14 by John Arbash Meinel
Move the tests over to testing the LazyGroupContentManager object.
709
    }
3735.32.8 by John Arbash Meinel
Some tests for the LazyGroupCompressFactory
710
    def make_block(self, key_to_text):
711
        """Create a GroupCompressBlock, filling it with the given texts."""
712
        compressor = groupcompress.GroupCompressor()
713
        start = 0
714
        for key in sorted(key_to_text):
715
            compressor.compress(key, key_to_text[key], None)
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
716
        locs = dict((key, (start, end)) for key, (start, _, end, _)
717
                    in compressor.labels_deltas.iteritems())
3735.32.23 by John Arbash Meinel
Add a _LazyGroupContentManager._check_rebuild_block
718
        block = compressor.flush()
719
        raw_bytes = block.to_bytes()
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
720
        return locs, groupcompress.GroupCompressBlock.from_bytes(raw_bytes)
3735.32.8 by John Arbash Meinel
Some tests for the LazyGroupCompressFactory
721
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
722
    def add_key_to_manager(self, key, locations, block, manager):
723
        start, end = locations[key]
724
        manager.add_factory(key, (), start, end)
3735.32.15 by John Arbash Meinel
Change the GroupCompressBlock code to allow not recording 'end'.
725
3735.32.8 by John Arbash Meinel
Some tests for the LazyGroupCompressFactory
726
    def test_get_fulltexts(self):
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
727
        locations, block = self.make_block(self._texts)
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
728
        manager = groupcompress._LazyGroupContentManager(block)
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
729
        self.add_key_to_manager(('key1',), locations, block, manager)
730
        self.add_key_to_manager(('key2',), locations, block, manager)
3735.32.15 by John Arbash Meinel
Change the GroupCompressBlock code to allow not recording 'end'.
731
        result_order = []
732
        for record in manager.get_record_stream():
733
            result_order.append(record.key)
734
            text = self._texts[record.key]
735
            self.assertEqual(text, record.get_bytes_as('fulltext'))
736
        self.assertEqual([('key1',), ('key2',)], result_order)
737
738
        # If we build the manager in the opposite order, we should get them
739
        # back in the opposite order
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
740
        manager = groupcompress._LazyGroupContentManager(block)
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
741
        self.add_key_to_manager(('key2',), locations, block, manager)
742
        self.add_key_to_manager(('key1',), locations, block, manager)
3735.32.15 by John Arbash Meinel
Change the GroupCompressBlock code to allow not recording 'end'.
743
        result_order = []
744
        for record in manager.get_record_stream():
745
            result_order.append(record.key)
746
            text = self._texts[record.key]
747
            self.assertEqual(text, record.get_bytes_as('fulltext'))
748
        self.assertEqual([('key2',), ('key1',)], result_order)
749
3735.32.16 by John Arbash Meinel
We now have a general header for the GC block.
750
    def test__wire_bytes_no_keys(self):
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
751
        locations, block = self.make_block(self._texts)
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
752
        manager = groupcompress._LazyGroupContentManager(block)
3735.32.16 by John Arbash Meinel
We now have a general header for the GC block.
753
        wire_bytes = manager._wire_bytes()
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
754
        block_length = len(block.to_bytes())
3735.32.24 by John Arbash Meinel
_wire_bytes() now strips groups as necessary, as does _insert_record_stream
755
        # We should have triggered a strip, since we aren't using any content
756
        stripped_block = manager._block.to_bytes()
757
        self.assertTrue(block_length > len(stripped_block))
758
        empty_z_header = zlib.compress('')
759
        self.assertEqual('groupcompress-block\n'
760
                         '8\n' # len(compress(''))
761
                         '0\n' # len('')
762
                         '%d\n'# compressed block len
763
                         '%s'  # zheader
764
                         '%s'  # block
765
                         % (len(stripped_block), empty_z_header,
766
                            stripped_block),
767
                         wire_bytes)
3735.32.16 by John Arbash Meinel
We now have a general header for the GC block.
768
3735.32.15 by John Arbash Meinel
Change the GroupCompressBlock code to allow not recording 'end'.
769
    def test__wire_bytes(self):
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
770
        locations, block = self.make_block(self._texts)
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
771
        manager = groupcompress._LazyGroupContentManager(block)
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
772
        self.add_key_to_manager(('key1',), locations, block, manager)
773
        self.add_key_to_manager(('key4',), locations, block, manager)
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
774
        block_bytes = block.to_bytes()
3735.32.16 by John Arbash Meinel
We now have a general header for the GC block.
775
        wire_bytes = manager._wire_bytes()
776
        (storage_kind, z_header_len, header_len,
777
         block_len, rest) = wire_bytes.split('\n', 4)
778
        z_header_len = int(z_header_len)
779
        header_len = int(header_len)
780
        block_len = int(block_len)
781
        self.assertEqual('groupcompress-block', storage_kind)
3735.38.2 by John Arbash Meinel
Make the text for key4 slightly longer, rather than include key3.
782
        self.assertEqual(33, z_header_len)
783
        self.assertEqual(25, header_len)
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
784
        self.assertEqual(len(block_bytes), block_len)
3735.32.16 by John Arbash Meinel
We now have a general header for the GC block.
785
        z_header = rest[:z_header_len]
786
        header = zlib.decompress(z_header)
787
        self.assertEqual(header_len, len(header))
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
788
        entry1 = locations[('key1',)]
789
        entry4 = locations[('key4',)]
3735.32.16 by John Arbash Meinel
We now have a general header for the GC block.
790
        self.assertEqualDiff('key1\n'
791
                             '\n'  # no parents
792
                             '%d\n' # start offset
3735.38.2 by John Arbash Meinel
Make the text for key4 slightly longer, rather than include key3.
793
                             '%d\n' # end offset
3735.32.16 by John Arbash Meinel
We now have a general header for the GC block.
794
                             'key4\n'
795
                             '\n'
796
                             '%d\n'
797
                             '%d\n'
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
798
                             % (entry1[0], entry1[1],
799
                                entry4[0], entry4[1]),
3735.32.16 by John Arbash Meinel
We now have a general header for the GC block.
800
                            header)
801
        z_block = rest[z_header_len:]
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
802
        self.assertEqual(block_bytes, z_block)
803
804
    def test_from_bytes(self):
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
805
        locations, block = self.make_block(self._texts)
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
806
        manager = groupcompress._LazyGroupContentManager(block)
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
807
        self.add_key_to_manager(('key1',), locations, block, manager)
808
        self.add_key_to_manager(('key4',), locations, block, manager)
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
809
        wire_bytes = manager._wire_bytes()
810
        self.assertStartsWith(wire_bytes, 'groupcompress-block\n')
3735.32.18 by John Arbash Meinel
We now support generating a network stream.
811
        manager = groupcompress._LazyGroupContentManager.from_bytes(wire_bytes)
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
812
        self.assertIsInstance(manager, groupcompress._LazyGroupContentManager)
3735.38.2 by John Arbash Meinel
Make the text for key4 slightly longer, rather than include key3.
813
        self.assertEqual(2, len(manager._factories))
3735.32.17 by John Arbash Meinel
We now round-trip the wire_bytes.
814
        self.assertEqual(block._z_content, manager._block._z_content)
815
        result_order = []
816
        for record in manager.get_record_stream():
817
            result_order.append(record.key)
818
            text = self._texts[record.key]
819
            self.assertEqual(text, record.get_bytes_as('fulltext'))
3735.38.2 by John Arbash Meinel
Make the text for key4 slightly longer, rather than include key3.
820
        self.assertEqual([('key1',), ('key4',)], result_order)
3735.32.23 by John Arbash Meinel
Add a _LazyGroupContentManager._check_rebuild_block
821
822
    def test__check_rebuild_no_changes(self):
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
823
        locations, block = self.make_block(self._texts)
3735.32.23 by John Arbash Meinel
Add a _LazyGroupContentManager._check_rebuild_block
824
        manager = groupcompress._LazyGroupContentManager(block)
825
        # Request all the keys, which ensures that we won't rebuild
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
826
        self.add_key_to_manager(('key1',), locations, block, manager)
827
        self.add_key_to_manager(('key2',), locations, block, manager)
828
        self.add_key_to_manager(('key3',), locations, block, manager)
829
        self.add_key_to_manager(('key4',), locations, block, manager)
3735.32.23 by John Arbash Meinel
Add a _LazyGroupContentManager._check_rebuild_block
830
        manager._check_rebuild_block()
831
        self.assertIs(block, manager._block)
832
833
    def test__check_rebuild_only_one(self):
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
834
        locations, block = self.make_block(self._texts)
3735.32.23 by John Arbash Meinel
Add a _LazyGroupContentManager._check_rebuild_block
835
        manager = groupcompress._LazyGroupContentManager(block)
836
        # Request just the first key, which should trigger a 'strip' action
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
837
        self.add_key_to_manager(('key1',), locations, block, manager)
3735.32.23 by John Arbash Meinel
Add a _LazyGroupContentManager._check_rebuild_block
838
        manager._check_rebuild_block()
839
        self.assertIsNot(block, manager._block)
840
        self.assertTrue(block._content_length > manager._block._content_length)
841
        # We should be able to still get the content out of this block, though
842
        # it should only have 1 entry
843
        for record in manager.get_record_stream():
844
            self.assertEqual(('key1',), record.key)
845
            self.assertEqual(self._texts[record.key],
846
                             record.get_bytes_as('fulltext'))
847
848
    def test__check_rebuild_middle(self):
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
849
        locations, block = self.make_block(self._texts)
3735.32.23 by John Arbash Meinel
Add a _LazyGroupContentManager._check_rebuild_block
850
        manager = groupcompress._LazyGroupContentManager(block)
851
        # Request a small key in the middle should trigger a 'rebuild'
3735.40.18 by John Arbash Meinel
Get rid of the entries dict in GroupCompressBlock.
852
        self.add_key_to_manager(('key4',), locations, block, manager)
3735.32.23 by John Arbash Meinel
Add a _LazyGroupContentManager._check_rebuild_block
853
        manager._check_rebuild_block()
854
        self.assertIsNot(block, manager._block)
855
        self.assertTrue(block._content_length > manager._block._content_length)
856
        for record in manager.get_record_stream():
857
            self.assertEqual(('key4',), record.key)
858
            self.assertEqual(self._texts[record.key],
859
                             record.get_bytes_as('fulltext'))