~bzr-pqm/bzr/bzr.dev

3641.3.29 by John Arbash Meinel
Cleanup the copyright headers
1
# Copyright (C) 2008 Canonical Ltd
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
2
#
3
# This program is free software; you can redistribute it and/or modify
3641.3.29 by John Arbash Meinel
Cleanup the copyright headers
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.
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
3641.3.29 by John Arbash Meinel
Cleanup the copyright headers
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
16
#
17
18
"""Tests for btree indices."""
19
3641.3.6 by John Arbash Meinel
Dropping the number of nodes to 100k from 200k
20
import pprint
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
21
import zlib
22
23
from bzrlib import (
24
    btree_index,
25
    errors,
26
    tests,
27
    )
28
from bzrlib.tests import (
29
    TestCaseWithTransport,
30
    TestScenarioApplier,
31
    adapt_tests,
32
    condition_isinstance,
33
    split_suite_by_condition,
34
    )
35
from bzrlib.transport import get_transport
36
37
38
def load_tests(standard_tests, module, loader):
39
    # parameterise the TestBTreeNodes tests
40
    node_tests, others = split_suite_by_condition(standard_tests,
41
        condition_isinstance(TestBTreeNodes))
42
    applier = TestScenarioApplier()
3641.3.30 by John Arbash Meinel
Rename _parse_btree to _btree_serializer
43
    import bzrlib._btree_serializer_py as py_module
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
44
    applier.scenarios = [('python', {'parse_btree': py_module})]
45
    if CompiledBtreeParserFeature.available():
46
        # Is there a way to do this that gets missing feature failures rather
47
        # than no indication to the user?
3641.3.30 by John Arbash Meinel
Rename _parse_btree to _btree_serializer
48
        import bzrlib._btree_serializer_c as c_module
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
49
        applier.scenarios.append(('C', {'parse_btree': c_module}))
50
    adapt_tests(node_tests, applier, others)
51
    return others
52
53
54
class _CompiledBtreeParserFeature(tests.Feature):
55
    def _probe(self):
56
        try:
3641.3.30 by John Arbash Meinel
Rename _parse_btree to _btree_serializer
57
            import bzrlib._btree_serializer_c
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
58
        except ImportError:
59
            return False
60
        return True
61
62
    def feature_name(self):
3641.3.30 by John Arbash Meinel
Rename _parse_btree to _btree_serializer
63
        return 'bzrlib._btree_serializer_c'
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
64
65
CompiledBtreeParserFeature = _CompiledBtreeParserFeature()
66
67
68
class BTreeTestCase(TestCaseWithTransport):
69
    # test names here are suffixed by the key length and reference list count
70
    # that they test.
71
72
    def setUp(self):
73
        TestCaseWithTransport.setUp(self)
74
        self._original_header = btree_index._RESERVED_HEADER_BYTES
75
        def restore():
76
            btree_index._RESERVED_HEADER_BYTES = self._original_header
77
        self.addCleanup(restore)
78
        btree_index._RESERVED_HEADER_BYTES = 100
79
80
    def make_nodes(self, count, key_elements, reference_lists):
81
        """Generate count*key_elements sample nodes."""
82
        keys = []
83
        for prefix_pos in range(key_elements):
84
            if key_elements - 1:
85
                prefix = (str(prefix_pos) * 40,)
86
            else:
87
                prefix = ()
3641.3.6 by John Arbash Meinel
Dropping the number of nodes to 100k from 200k
88
            for pos in xrange(count):
89
                # TODO: This creates odd keys. When count == 100,000, it
90
                #       creates a 240 byte key
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
91
                key = prefix + (str(pos) * 40,)
92
                value = "value:%s" % pos
93
                if reference_lists:
94
                    # generate some references
95
                    refs = []
96
                    for list_pos in range(reference_lists):
97
                        # as many keys in each list as its index + the key depth
98
                        # mod 2 - this generates both 0 length lists and
99
                        # ones slightly longer than the number of lists.
3641.3.6 by John Arbash Meinel
Dropping the number of nodes to 100k from 200k
100
                        # It also ensures we have non homogeneous lists.
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
101
                        refs.append([])
102
                        for ref_pos in range(list_pos + pos % 2):
103
                            if pos % 2:
104
                                # refer to a nearby key
105
                                refs[-1].append(prefix + ("ref" + str(pos - 1) * 40,))
106
                            else:
107
                                # serial of this ref in the ref list
108
                                refs[-1].append(prefix + ("ref" + str(ref_pos) * 40,))
109
                        refs[-1] = tuple(refs[-1])
110
                    refs = tuple(refs)
111
                else:
112
                    refs = ()
113
                keys.append((key, value, refs))
114
        return keys
115
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
116
    def shrink_page_size(self):
117
        """Shrink the default page size so that less fits in a page."""
118
        old_page_size = btree_index._PAGE_SIZE
119
        def cleanup():
120
            btree_index._PAGE_SIZE = old_page_size
121
        self.addCleanup(cleanup)
122
        btree_index._PAGE_SIZE = 2048
123
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
124
125
class TestBTreeBuilder(BTreeTestCase):
126
127
    def test_empty_1_0(self):
128
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
129
        # NamedTemporaryFile dies on builder.finish().read(). weird.
130
        temp_file = builder.finish()
131
        content = temp_file.read()
132
        del temp_file
133
        self.assertEqual(
3641.3.3 by John Arbash Meinel
Change the header to indicate these indexes are
134
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=0\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
135
            "row_lengths=\n",
136
            content)
137
138
    def test_empty_2_1(self):
139
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=1)
140
        # NamedTemporaryFile dies on builder.finish().read(). weird.
141
        temp_file = builder.finish()
142
        content = temp_file.read()
143
        del temp_file
144
        self.assertEqual(
3641.3.3 by John Arbash Meinel
Change the header to indicate these indexes are
145
            "B+Tree Graph Index 2\nnode_ref_lists=1\nkey_elements=2\nlen=0\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
146
            "row_lengths=\n",
147
            content)
148
149
    def test_root_leaf_1_0(self):
150
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
151
        nodes = self.make_nodes(5, 1, 0)
152
        for node in nodes:
153
            builder.add_node(*node)
154
        # NamedTemporaryFile dies on builder.finish().read(). weird.
155
        temp_file = builder.finish()
156
        content = temp_file.read()
157
        del temp_file
158
        self.assertEqual(158, len(content))
159
        self.assertEqual(
3641.3.3 by John Arbash Meinel
Change the header to indicate these indexes are
160
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=5\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
161
            "row_lengths=1\n",
162
            content[:73])
163
        node_content = content[73:]
164
        node_bytes = zlib.decompress(node_content)
165
        expected_node = ("type=leaf\n"
166
            "0000000000000000000000000000000000000000\x00\x00value:0\n"
167
            "1111111111111111111111111111111111111111\x00\x00value:1\n"
168
            "2222222222222222222222222222222222222222\x00\x00value:2\n"
169
            "3333333333333333333333333333333333333333\x00\x00value:3\n"
170
            "4444444444444444444444444444444444444444\x00\x00value:4\n")
171
        self.assertEqual(expected_node, node_bytes)
172
173
    def test_root_leaf_2_2(self):
174
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
175
        nodes = self.make_nodes(5, 2, 2)
176
        for node in nodes:
177
            builder.add_node(*node)
178
        # NamedTemporaryFile dies on builder.finish().read(). weird.
179
        temp_file = builder.finish()
180
        content = temp_file.read()
181
        del temp_file
182
        self.assertEqual(264, len(content))
183
        self.assertEqual(
3641.3.3 by John Arbash Meinel
Change the header to indicate these indexes are
184
            "B+Tree Graph Index 2\nnode_ref_lists=2\nkey_elements=2\nlen=10\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
185
            "row_lengths=1\n",
186
            content[:74])
187
        node_content = content[74:]
188
        node_bytes = zlib.decompress(node_content)
189
        expected_node = (
190
            "type=leaf\n"
191
            "0000000000000000000000000000000000000000\x000000000000000000000000000000000000000000\x00\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:0\n"
192
            "0000000000000000000000000000000000000000\x001111111111111111111111111111111111111111\x000000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\r0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:1\n"
193
            "0000000000000000000000000000000000000000\x002222222222222222222222222222222222222222\x00\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:2\n"
194
            "0000000000000000000000000000000000000000\x003333333333333333333333333333333333333333\x000000000000000000000000000000000000000000\x00ref2222222222222222222222222222222222222222\t0000000000000000000000000000000000000000\x00ref2222222222222222222222222222222222222222\r0000000000000000000000000000000000000000\x00ref2222222222222222222222222222222222222222\x00value:3\n"
195
            "0000000000000000000000000000000000000000\x004444444444444444444444444444444444444444\x00\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:4\n"
196
            "1111111111111111111111111111111111111111\x000000000000000000000000000000000000000000\x00\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:0\n"
197
            "1111111111111111111111111111111111111111\x001111111111111111111111111111111111111111\x001111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\r1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:1\n"
198
            "1111111111111111111111111111111111111111\x002222222222222222222222222222222222222222\x00\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:2\n"
199
            "1111111111111111111111111111111111111111\x003333333333333333333333333333333333333333\x001111111111111111111111111111111111111111\x00ref2222222222222222222222222222222222222222\t1111111111111111111111111111111111111111\x00ref2222222222222222222222222222222222222222\r1111111111111111111111111111111111111111\x00ref2222222222222222222222222222222222222222\x00value:3\n"
200
            "1111111111111111111111111111111111111111\x004444444444444444444444444444444444444444\x00\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:4\n"
201
            ""
202
            )
203
        self.assertEqual(expected_node, node_bytes)
204
205
    def test_2_leaves_1_0(self):
206
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
207
        nodes = self.make_nodes(400, 1, 0)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
208
        for node in nodes:
209
            builder.add_node(*node)
210
        # NamedTemporaryFile dies on builder.finish().read(). weird.
211
        temp_file = builder.finish()
212
        content = temp_file.read()
213
        del temp_file
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
214
        self.assertEqual(9283, len(content))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
215
        self.assertEqual(
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
216
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=400\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
217
            "row_lengths=1,2\n",
218
            content[:77])
219
        root = content[77:4096]
220
        leaf1 = content[4096:8192]
221
        leaf2 = content[8192:]
222
        root_bytes = zlib.decompress(root)
223
        expected_root = (
224
            "type=internal\n"
225
            "offset=0\n"
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
226
            ) + ("307" * 40) + "\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
227
        self.assertEqual(expected_root, root_bytes)
228
        # We already know serialisation works for leaves, check key selection:
229
        leaf1_bytes = zlib.decompress(leaf1)
230
        sorted_node_keys = sorted(node[0] for node in nodes)
231
        node = btree_index._LeafNode(leaf1_bytes, 1, 0)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
232
        self.assertEqual(231, len(node.keys))
233
        self.assertEqual(sorted_node_keys[:231], sorted(node.keys))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
234
        leaf2_bytes = zlib.decompress(leaf2)
235
        node = btree_index._LeafNode(leaf2_bytes, 1, 0)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
236
        self.assertEqual(400 - 231, len(node.keys))
237
        self.assertEqual(sorted_node_keys[231:], sorted(node.keys))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
238
239
    def test_last_page_rounded_1_layer(self):
240
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
241
        nodes = self.make_nodes(10, 1, 0)
242
        for node in nodes:
243
            builder.add_node(*node)
244
        # NamedTemporaryFile dies on builder.finish().read(). weird.
245
        temp_file = builder.finish()
246
        content = temp_file.read()
247
        del temp_file
248
        self.assertEqual(181, len(content))
249
        self.assertEqual(
3641.3.3 by John Arbash Meinel
Change the header to indicate these indexes are
250
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=10\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
251
            "row_lengths=1\n",
252
            content[:74])
253
        # Check thelast page is well formed
254
        leaf2 = content[74:]
255
        leaf2_bytes = zlib.decompress(leaf2)
256
        node = btree_index._LeafNode(leaf2_bytes, 1, 0)
257
        self.assertEqual(10, len(node.keys))
258
        sorted_node_keys = sorted(node[0] for node in nodes)
259
        self.assertEqual(sorted_node_keys, sorted(node.keys))
260
261
    def test_last_page_not_rounded_2_layer(self):
262
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
263
        nodes = self.make_nodes(400, 1, 0)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
264
        for node in nodes:
265
            builder.add_node(*node)
266
        # NamedTemporaryFile dies on builder.finish().read(). weird.
267
        temp_file = builder.finish()
268
        content = temp_file.read()
269
        del temp_file
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
270
        self.assertEqual(9283, len(content))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
271
        self.assertEqual(
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
272
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=400\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
273
            "row_lengths=1,2\n",
274
            content[:77])
3641.3.30 by John Arbash Meinel
Rename _parse_btree to _btree_serializer
275
        # Check the last page is well formed
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
276
        leaf2 = content[8192:]
277
        leaf2_bytes = zlib.decompress(leaf2)
278
        node = btree_index._LeafNode(leaf2_bytes, 1, 0)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
279
        self.assertEqual(400 - 231, len(node.keys))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
280
        sorted_node_keys = sorted(node[0] for node in nodes)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
281
        self.assertEqual(sorted_node_keys[231:], sorted(node.keys))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
282
283
    def test_three_level_tree_details(self):
284
        # The left most pointer in the second internal node in a row should
285
        # pointer to the second node that the internal node is for, _not_
286
        # the first, otherwise the first node overlaps with the last node of
287
        # the prior internal node on that row.
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
288
        self.shrink_page_size()
289
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
290
        # 40K nodes is enough to create a two internal nodes on the second
291
        # level, with a 2K page size
292
        nodes = self.make_nodes(20000, 2, 2)
3641.3.5 by John Arbash Meinel
For iter_all and three_level tests adjust spill-at.
293
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
294
        for node in nodes:
295
            builder.add_node(*node)
296
        transport = get_transport('trace+' + self.get_url(''))
3641.3.7 by John Arbash Meinel
Make it easier to profile the btree writer time
297
        size = transport.put_file('index', self.time(builder.finish))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
298
        del builder
299
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
300
        # Seed the metadata, we're using internal calls now.
301
        index.key_count()
302
        self.assertEqual(3, len(index._row_lengths),
303
            "Not enough rows: %r" % index._row_lengths)
304
        self.assertEqual(4, len(index._row_offsets))
305
        self.assertEqual(sum(index._row_lengths), index._row_offsets[-1])
306
        internal_nodes = index._get_internal_nodes([0, 1, 2])
307
        root_node = internal_nodes[0]
308
        internal_node1 = internal_nodes[1]
309
        internal_node2 = internal_nodes[2]
310
        # The left most node node2 points at should be one after the right most
311
        # node pointed at by node1.
312
        self.assertEqual(internal_node2.offset, 1 + len(internal_node1.keys))
313
        # The left most key of the second node pointed at by internal_node2
314
        # should be its first key. We can check this by looking for its first key
315
        # in the second node it points at
316
        pos = index._row_offsets[2] + internal_node2.offset + 1
317
        leaf = index._get_leaf_nodes([pos])[pos]
318
        self.assertTrue(internal_node2.keys[0] in leaf.keys)
319
320
    def test_2_leaves_2_2(self):
321
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
322
        nodes = self.make_nodes(100, 2, 2)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
323
        for node in nodes:
324
            builder.add_node(*node)
325
        # NamedTemporaryFile dies on builder.finish().read(). weird.
326
        temp_file = builder.finish()
327
        content = temp_file.read()
328
        del temp_file
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
329
        self.assertEqual(12643, len(content))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
330
        self.assertEqual(
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
331
            "B+Tree Graph Index 2\nnode_ref_lists=2\nkey_elements=2\nlen=200\n"
332
            "row_lengths=1,3\n",
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
333
            content[:77])
334
        root = content[77:4096]
335
        leaf1 = content[4096:8192]
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
336
        leaf2 = content[8192:12288]
337
        leaf3 = content[12288:]
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
338
        root_bytes = zlib.decompress(root)
339
        expected_root = (
340
            "type=internal\n"
341
            "offset=0\n"
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
342
            + ("0" * 40) + "\x00" + ("91" * 40) + "\n"
343
            + ("1" * 40) + "\x00" + ("81" * 40) + "\n"
344
            )
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
345
        self.assertEqual(expected_root, root_bytes)
346
        # We assume the other leaf nodes have been written correctly - layering
347
        # FTW.
348
349
    def test_spill_index_stress_1_1(self):
350
        builder = btree_index.BTreeBuilder(key_elements=1, spill_at=2)
351
        nodes = [node[0:2] for node in self.make_nodes(16, 1, 0)]
352
        builder.add_node(*nodes[0])
353
        # Test the parts of the index that take up memory are doing so
354
        # predictably.
355
        self.assertEqual(1, len(builder._nodes))
356
        self.assertEqual(1, len(builder._keys))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
357
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
358
        builder.add_node(*nodes[1])
359
        self.assertEqual(0, len(builder._nodes))
360
        self.assertEqual(0, len(builder._keys))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
361
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
362
        self.assertEqual(1, len(builder._backing_indices))
363
        self.assertEqual(2, builder._backing_indices[0].key_count())
364
        # now back to memory
365
        builder.add_node(*nodes[2])
366
        self.assertEqual(1, len(builder._nodes))
367
        self.assertEqual(1, len(builder._keys))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
368
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
369
        # And spills to a second backing index combing all
370
        builder.add_node(*nodes[3])
371
        self.assertEqual(0, len(builder._nodes))
372
        self.assertEqual(0, len(builder._keys))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
373
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
374
        self.assertEqual(2, len(builder._backing_indices))
375
        self.assertEqual(None, builder._backing_indices[0])
376
        self.assertEqual(4, builder._backing_indices[1].key_count())
377
        # The next spills to the 2-len slot
378
        builder.add_node(*nodes[4])
379
        builder.add_node(*nodes[5])
380
        self.assertEqual(0, len(builder._nodes))
381
        self.assertEqual(0, len(builder._keys))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
382
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
383
        self.assertEqual(2, len(builder._backing_indices))
384
        self.assertEqual(2, builder._backing_indices[0].key_count())
385
        self.assertEqual(4, builder._backing_indices[1].key_count())
386
        # Next spill combines
387
        builder.add_node(*nodes[6])
388
        builder.add_node(*nodes[7])
389
        self.assertEqual(3, len(builder._backing_indices))
390
        self.assertEqual(None, builder._backing_indices[0])
391
        self.assertEqual(None, builder._backing_indices[1])
392
        self.assertEqual(8, builder._backing_indices[2].key_count())
393
        # And so forth - counting up in binary.
394
        builder.add_node(*nodes[8])
395
        builder.add_node(*nodes[9])
396
        self.assertEqual(3, len(builder._backing_indices))
397
        self.assertEqual(2, builder._backing_indices[0].key_count())
398
        self.assertEqual(None, builder._backing_indices[1])
399
        self.assertEqual(8, builder._backing_indices[2].key_count())
400
        builder.add_node(*nodes[10])
401
        builder.add_node(*nodes[11])
402
        self.assertEqual(3, len(builder._backing_indices))
403
        self.assertEqual(None, builder._backing_indices[0])
404
        self.assertEqual(4, builder._backing_indices[1].key_count())
405
        self.assertEqual(8, builder._backing_indices[2].key_count())
406
        builder.add_node(*nodes[12])
407
        # Test that memory and disk are both used for query methods; and that
408
        # None is skipped over happily.
409
        self.assertEqual([(builder,) + node for node in sorted(nodes[:13])],
410
            list(builder.iter_all_entries()))
411
        # Two nodes - one memory one disk
412
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
413
            set(builder.iter_entries([nodes[12][0], nodes[11][0]])))
414
        self.assertEqual(13, builder.key_count())
415
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
416
            set(builder.iter_entries_prefix([nodes[12][0], nodes[11][0]])))
417
        builder.add_node(*nodes[13])
418
        self.assertEqual(3, len(builder._backing_indices))
419
        self.assertEqual(2, builder._backing_indices[0].key_count())
420
        self.assertEqual(4, builder._backing_indices[1].key_count())
421
        self.assertEqual(8, builder._backing_indices[2].key_count())
422
        builder.add_node(*nodes[14])
423
        builder.add_node(*nodes[15])
424
        self.assertEqual(4, len(builder._backing_indices))
425
        self.assertEqual(None, builder._backing_indices[0])
426
        self.assertEqual(None, builder._backing_indices[1])
427
        self.assertEqual(None, builder._backing_indices[2])
428
        self.assertEqual(16, builder._backing_indices[3].key_count())
429
        # Now finish, and check we got a correctly ordered tree
430
        transport = self.get_transport('')
431
        size = transport.put_file('index', builder.finish())
432
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
433
        nodes = list(index.iter_all_entries())
434
        self.assertEqual(sorted(nodes), nodes)
435
        self.assertEqual(16, len(nodes))
436
437
    def test_spill_index_stress_2_2(self):
438
        # test that references and longer keys don't confuse things.
439
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2,
440
            spill_at=2)
441
        nodes = self.make_nodes(16, 2, 2)
442
        builder.add_node(*nodes[0])
443
        # Test the parts of the index that take up memory are doing so
444
        # predictably.
445
        self.assertEqual(1, len(builder._keys))
3644.2.1 by John Arbash Meinel
Change the IndexBuilders to not generate the nodes_by_key unless needed.
446
        self.assertEqual(1, len(builder._nodes))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
447
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
448
        builder.add_node(*nodes[1])
449
        self.assertEqual(0, len(builder._keys))
450
        self.assertEqual(0, len(builder._nodes))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
451
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
452
        self.assertEqual(1, len(builder._backing_indices))
453
        self.assertEqual(2, builder._backing_indices[0].key_count())
454
        # now back to memory
3644.2.6 by John Arbash Meinel
Restore the exact old tests, only assert that
455
        old = dict(builder._get_nodes_by_key()) #Build up the nodes by key dict
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
456
        builder.add_node(*nodes[2])
3644.2.1 by John Arbash Meinel
Change the IndexBuilders to not generate the nodes_by_key unless needed.
457
        self.assertEqual(1, len(builder._nodes))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
458
        self.assertEqual(1, len(builder._keys))
3644.2.6 by John Arbash Meinel
Restore the exact old tests, only assert that
459
        self.assertIsNot(None, builder._nodes_by_key)
460
        self.assertNotEqual({}, builder._nodes_by_key)
461
        # We should have a new entry
462
        self.assertNotEqual(old, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
463
        # And spills to a second backing index combing all
464
        builder.add_node(*nodes[3])
465
        self.assertEqual(0, len(builder._nodes))
466
        self.assertEqual(0, len(builder._keys))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
467
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
468
        self.assertEqual(2, len(builder._backing_indices))
469
        self.assertEqual(None, builder._backing_indices[0])
470
        self.assertEqual(4, builder._backing_indices[1].key_count())
471
        # The next spills to the 2-len slot
472
        builder.add_node(*nodes[4])
473
        builder.add_node(*nodes[5])
474
        self.assertEqual(0, len(builder._nodes))
475
        self.assertEqual(0, len(builder._keys))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
476
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
477
        self.assertEqual(2, len(builder._backing_indices))
478
        self.assertEqual(2, builder._backing_indices[0].key_count())
479
        self.assertEqual(4, builder._backing_indices[1].key_count())
480
        # Next spill combines
481
        builder.add_node(*nodes[6])
482
        builder.add_node(*nodes[7])
483
        self.assertEqual(3, len(builder._backing_indices))
484
        self.assertEqual(None, builder._backing_indices[0])
485
        self.assertEqual(None, builder._backing_indices[1])
486
        self.assertEqual(8, builder._backing_indices[2].key_count())
487
        # And so forth - counting up in binary.
488
        builder.add_node(*nodes[8])
489
        builder.add_node(*nodes[9])
490
        self.assertEqual(3, len(builder._backing_indices))
491
        self.assertEqual(2, builder._backing_indices[0].key_count())
492
        self.assertEqual(None, builder._backing_indices[1])
493
        self.assertEqual(8, builder._backing_indices[2].key_count())
494
        builder.add_node(*nodes[10])
495
        builder.add_node(*nodes[11])
496
        self.assertEqual(3, len(builder._backing_indices))
497
        self.assertEqual(None, builder._backing_indices[0])
498
        self.assertEqual(4, builder._backing_indices[1].key_count())
499
        self.assertEqual(8, builder._backing_indices[2].key_count())
500
        builder.add_node(*nodes[12])
501
        # Test that memory and disk are both used for query methods; and that
502
        # None is skipped over happily.
503
        self.assertEqual([(builder,) + node for node in sorted(nodes[:13])],
504
            list(builder.iter_all_entries()))
505
        # Two nodes - one memory one disk
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
506
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
507
            set(builder.iter_entries([nodes[12][0], nodes[11][0]])))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
508
        self.assertEqual(13, builder.key_count())
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
509
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
510
            set(builder.iter_entries_prefix([nodes[12][0], nodes[11][0]])))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
511
        builder.add_node(*nodes[13])
512
        self.assertEqual(3, len(builder._backing_indices))
513
        self.assertEqual(2, builder._backing_indices[0].key_count())
514
        self.assertEqual(4, builder._backing_indices[1].key_count())
515
        self.assertEqual(8, builder._backing_indices[2].key_count())
516
        builder.add_node(*nodes[14])
517
        builder.add_node(*nodes[15])
518
        self.assertEqual(4, len(builder._backing_indices))
519
        self.assertEqual(None, builder._backing_indices[0])
520
        self.assertEqual(None, builder._backing_indices[1])
521
        self.assertEqual(None, builder._backing_indices[2])
522
        self.assertEqual(16, builder._backing_indices[3].key_count())
523
        # Now finish, and check we got a correctly ordered tree
524
        transport = self.get_transport('')
525
        size = transport.put_file('index', builder.finish())
526
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
527
        nodes = list(index.iter_all_entries())
528
        self.assertEqual(sorted(nodes), nodes)
529
        self.assertEqual(16, len(nodes))
530
531
    def test_spill_index_duplicate_key_caught_on_finish(self):
532
        builder = btree_index.BTreeBuilder(key_elements=1, spill_at=2)
533
        nodes = [node[0:2] for node in self.make_nodes(16, 1, 0)]
534
        builder.add_node(*nodes[0])
535
        builder.add_node(*nodes[1])
536
        builder.add_node(*nodes[0])
537
        self.assertRaises(errors.BadIndexDuplicateKey, builder.finish)
538
539
540
class TestBTreeIndex(BTreeTestCase):
541
542
    def make_index(self, ref_lists=0, key_elements=1, nodes=[]):
543
        builder = btree_index.BTreeBuilder(reference_lists=ref_lists,
544
            key_elements=key_elements)
545
        for key, value, references in nodes:
546
            builder.add_node(key, value, references)
547
        stream = builder.finish()
548
        trans = get_transport('trace+' + self.get_url())
549
        size = trans.put_file('index', stream)
550
        return btree_index.BTreeGraphIndex(trans, 'index', size)
551
552
    def test_trivial_constructor(self):
553
        transport = get_transport('trace+' + self.get_url(''))
554
        index = btree_index.BTreeGraphIndex(transport, 'index', None)
555
        # Checks the page size at load, but that isn't logged yet.
556
        self.assertEqual([], transport._activity)
557
558
    def test_with_size_constructor(self):
559
        transport = get_transport('trace+' + self.get_url(''))
560
        index = btree_index.BTreeGraphIndex(transport, 'index', 1)
561
        # Checks the page size at load, but that isn't logged yet.
562
        self.assertEqual([], transport._activity)
563
564
    def test_empty_key_count_no_size(self):
565
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
566
        transport = get_transport('trace+' + self.get_url(''))
567
        transport.put_file('index', builder.finish())
568
        index = btree_index.BTreeGraphIndex(transport, 'index', None)
569
        del transport._activity[:]
570
        self.assertEqual([], transport._activity)
571
        self.assertEqual(0, index.key_count())
572
        # The entire index should have been requested (as we generally have the
573
        # size available, and doing many small readvs is inappropriate).
574
        # We can't tell how much was actually read here, but - check the code.
575
        self.assertEqual([('get', 'index'),
576
            ('readv', 'index', [(0, 72)], False, None)],
577
            transport._activity)
578
579
    def test_empty_key_count(self):
580
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
581
        transport = get_transport('trace+' + self.get_url(''))
582
        size = transport.put_file('index', builder.finish())
583
        self.assertEqual(72, size)
584
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
585
        del transport._activity[:]
586
        self.assertEqual([], transport._activity)
587
        self.assertEqual(0, index.key_count())
588
        # The entire index should have been read, as 4K > size
589
        self.assertEqual([('readv', 'index', [(0, 72)], False, None)],
590
            transport._activity)
591
592
    def test_non_empty_key_count_2_2(self):
593
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
594
        nodes = self.make_nodes(35, 2, 2)
595
        for node in nodes:
596
            builder.add_node(*node)
597
        transport = get_transport('trace+' + self.get_url(''))
598
        size = transport.put_file('index', builder.finish())
599
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
600
        del transport._activity[:]
601
        self.assertEqual([], transport._activity)
602
        self.assertEqual(70, index.key_count())
603
        # The entire index should have been read, as it is one page long.
604
        self.assertEqual([('readv', 'index', [(0, size)], False, None)],
605
            transport._activity)
3641.5.8 by John Arbash Meinel
Fix up the test suite, now that we pack more efficiently.
606
        self.assertEqual(1199, size)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
607
608
    def test_2_levels_key_count_2_2(self):
609
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
610
        nodes = self.make_nodes(160, 2, 2)
611
        for node in nodes:
612
            builder.add_node(*node)
613
        transport = get_transport('trace+' + self.get_url(''))
614
        size = transport.put_file('index', builder.finish())
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
615
        self.assertEqual(17692, size)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
616
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
617
        del transport._activity[:]
618
        self.assertEqual([], transport._activity)
619
        self.assertEqual(320, index.key_count())
620
        # The entire index should not have been read.
621
        self.assertEqual([('readv', 'index', [(0, 4096)], False, None)],
622
            transport._activity)
623
624
    def test_validate_one_page(self):
625
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
626
        nodes = self.make_nodes(45, 2, 2)
627
        for node in nodes:
628
            builder.add_node(*node)
629
        transport = get_transport('trace+' + self.get_url(''))
630
        size = transport.put_file('index', builder.finish())
631
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
632
        del transport._activity[:]
633
        self.assertEqual([], transport._activity)
634
        index.validate()
635
        # The entire index should have been read linearly.
636
        self.assertEqual([('readv', 'index', [(0, size)], False, None)],
637
            transport._activity)
638
        self.assertEqual(1514, size)
639
640
    def test_validate_two_pages(self):
641
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
642
        nodes = self.make_nodes(80, 2, 2)
643
        for node in nodes:
644
            builder.add_node(*node)
645
        transport = get_transport('trace+' + self.get_url(''))
646
        size = transport.put_file('index', builder.finish())
647
        # Root page, 2 leaf pages
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
648
        self.assertEqual(9339, size)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
649
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
650
        del transport._activity[:]
651
        self.assertEqual([], transport._activity)
652
        index.validate()
653
        # The entire index should have been read linearly.
654
        self.assertEqual([('readv', 'index', [(0, 4096)], False, None),
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
655
            ('readv', 'index', [(4096, 4096), (8192, 1147)], False, None)],
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
656
            transport._activity)
657
        # XXX: TODO: write some badly-ordered nodes, and some pointers-to-wrong
658
        # node and make validate find them.
659
660
    def test_eq_ne(self):
661
        # two indices are equal when constructed with the same parameters:
662
        transport1 = get_transport('trace+' + self.get_url(''))
663
        transport2 = get_transport(self.get_url(''))
664
        self.assertTrue(
665
            btree_index.BTreeGraphIndex(transport1, 'index', None) ==
666
            btree_index.BTreeGraphIndex(transport1, 'index', None))
667
        self.assertTrue(
668
            btree_index.BTreeGraphIndex(transport1, 'index', 20) ==
669
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
670
        self.assertFalse(
671
            btree_index.BTreeGraphIndex(transport1, 'index', 20) ==
672
            btree_index.BTreeGraphIndex(transport2, 'index', 20))
673
        self.assertFalse(
674
            btree_index.BTreeGraphIndex(transport1, 'inde1', 20) ==
675
            btree_index.BTreeGraphIndex(transport1, 'inde2', 20))
676
        self.assertFalse(
677
            btree_index.BTreeGraphIndex(transport1, 'index', 10) ==
678
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
679
        self.assertFalse(
680
            btree_index.BTreeGraphIndex(transport1, 'index', None) !=
681
            btree_index.BTreeGraphIndex(transport1, 'index', None))
682
        self.assertFalse(
683
            btree_index.BTreeGraphIndex(transport1, 'index', 20) !=
684
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
685
        self.assertTrue(
686
            btree_index.BTreeGraphIndex(transport1, 'index', 20) !=
687
            btree_index.BTreeGraphIndex(transport2, 'index', 20))
688
        self.assertTrue(
689
            btree_index.BTreeGraphIndex(transport1, 'inde1', 20) !=
690
            btree_index.BTreeGraphIndex(transport1, 'inde2', 20))
691
        self.assertTrue(
692
            btree_index.BTreeGraphIndex(transport1, 'index', 10) !=
693
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
694
695
    def test_iter_all_entries_reads(self):
696
        # iterating all entries reads the header, then does a linear
697
        # read.
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
698
        self.shrink_page_size()
3641.5.11 by John Arbash Meinel
Some small test cleanup
699
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
700
        # 20k nodes is enough to create a two internal nodes on the second
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
701
        # level, with a 2K page size
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
702
        nodes = self.make_nodes(10000, 2, 2)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
703
        for node in nodes:
704
            builder.add_node(*node)
705
        transport = get_transport('trace+' + self.get_url(''))
706
        size = transport.put_file('index', builder.finish())
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
707
        self.assertEqual(1303220, size, 'number of expected bytes in the'
708
                                        ' output changed')
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
709
        page_size = btree_index._PAGE_SIZE
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
710
        del builder
711
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
712
        del transport._activity[:]
713
        self.assertEqual([], transport._activity)
3641.5.8 by John Arbash Meinel
Fix up the test suite, now that we pack more efficiently.
714
        found_nodes = self.time(list, index.iter_all_entries())
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
715
        bare_nodes = []
716
        for node in found_nodes:
717
            self.assertTrue(node[0] is index)
718
            bare_nodes.append(node[1:])
719
        self.assertEqual(3, len(index._row_lengths),
720
            "Not enough rows: %r" % index._row_lengths)
721
        # Should be as long as the nodes we supplied
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
722
        self.assertEqual(20000, len(found_nodes))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
723
        # Should have the same content
724
        self.assertEqual(set(nodes), set(bare_nodes))
725
        # Should have done linear scan IO up the index, ignoring
726
        # the internal nodes:
727
        # The entire index should have been read
728
        total_pages = sum(index._row_lengths)
729
        self.assertEqual(total_pages, index._row_offsets[-1])
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
730
        self.assertEqual(1303220, size)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
731
        # The start of the leaves
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
732
        first_byte = index._row_offsets[-2] * page_size
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
733
        readv_request = []
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
734
        for offset in range(first_byte, size, page_size):
735
            readv_request.append((offset, page_size))
3641.3.6 by John Arbash Meinel
Dropping the number of nodes to 100k from 200k
736
        # The last page is truncated
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
737
        readv_request[-1] = (readv_request[-1][0], 1303220 % page_size)
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
738
        expected = [('readv', 'index', [(0, page_size)], False, None),
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
739
             ('readv',  'index', readv_request, False, None)]
740
        if expected != transport._activity:
741
            self.assertEqualDiff(pprint.pformat(expected),
742
                                 pprint.pformat(transport._activity))
743
744
    def _test_iter_entries_references_resolved(self):
745
        index = self.make_index(1, nodes=[
746
            (('name', ), 'data', ([('ref', ), ('ref', )], )),
747
            (('ref', ), 'refdata', ([], ))])
748
        self.assertEqual(set([(index, ('name', ), 'data', ((('ref',),('ref',)),)),
749
            (index, ('ref', ), 'refdata', ((), ))]),
750
            set(index.iter_entries([('name',), ('ref',)])))
751
752
    def test_iter_entries_references_2_refs_resolved(self):
753
        # iterating some entries reads just the pages needed. For now, to
754
        # get it working and start measuring, only 4K pages are read.
755
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
756
        # 80 nodes is enough to create a two-level index.
757
        nodes = self.make_nodes(160, 2, 2)
758
        for node in nodes:
759
            builder.add_node(*node)
760
        transport = get_transport('trace+' + self.get_url(''))
761
        size = transport.put_file('index', builder.finish())
762
        del builder
763
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
764
        del transport._activity[:]
765
        self.assertEqual([], transport._activity)
766
        # search for one key
767
        found_nodes = list(index.iter_entries([nodes[30][0]]))
768
        bare_nodes = []
769
        for node in found_nodes:
770
            self.assertTrue(node[0] is index)
771
            bare_nodes.append(node[1:])
772
        # Should be as long as the nodes we supplied
773
        self.assertEqual(1, len(found_nodes))
774
        # Should have the same content
775
        self.assertEqual(nodes[30], bare_nodes[0])
776
        # Should have read the root node, then one leaf page:
777
        self.assertEqual([('readv', 'index', [(0, 4096)], False, None),
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
778
             ('readv',  'index', [(8192, 4096), ], False, None)],
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
779
            transport._activity)
780
781
    def test_iter_key_prefix_1_element_key_None(self):
782
        index = self.make_index()
783
        self.assertRaises(errors.BadIndexKey, list,
784
            index.iter_entries_prefix([(None, )]))
785
786
    def test_iter_key_prefix_wrong_length(self):
787
        index = self.make_index()
788
        self.assertRaises(errors.BadIndexKey, list,
789
            index.iter_entries_prefix([('foo', None)]))
790
        index = self.make_index(key_elements=2)
791
        self.assertRaises(errors.BadIndexKey, list,
792
            index.iter_entries_prefix([('foo', )]))
793
        self.assertRaises(errors.BadIndexKey, list,
794
            index.iter_entries_prefix([('foo', None, None)]))
795
796
    def test_iter_key_prefix_1_key_element_no_refs(self):
797
        index = self.make_index( nodes=[
798
            (('name', ), 'data', ()),
799
            (('ref', ), 'refdata', ())])
800
        self.assertEqual(set([(index, ('name', ), 'data'),
801
            (index, ('ref', ), 'refdata')]),
802
            set(index.iter_entries_prefix([('name', ), ('ref', )])))
803
804
    def test_iter_key_prefix_1_key_element_refs(self):
805
        index = self.make_index(1, nodes=[
806
            (('name', ), 'data', ([('ref', )], )),
807
            (('ref', ), 'refdata', ([], ))])
808
        self.assertEqual(set([(index, ('name', ), 'data', ((('ref',),),)),
809
            (index, ('ref', ), 'refdata', ((), ))]),
810
            set(index.iter_entries_prefix([('name', ), ('ref', )])))
811
812
    def test_iter_key_prefix_2_key_element_no_refs(self):
813
        index = self.make_index(key_elements=2, nodes=[
814
            (('name', 'fin1'), 'data', ()),
815
            (('name', 'fin2'), 'beta', ()),
816
            (('ref', 'erence'), 'refdata', ())])
817
        self.assertEqual(set([(index, ('name', 'fin1'), 'data'),
818
            (index, ('ref', 'erence'), 'refdata')]),
819
            set(index.iter_entries_prefix([('name', 'fin1'), ('ref', 'erence')])))
820
        self.assertEqual(set([(index, ('name', 'fin1'), 'data'),
821
            (index, ('name', 'fin2'), 'beta')]),
822
            set(index.iter_entries_prefix([('name', None)])))
823
824
    def test_iter_key_prefix_2_key_element_refs(self):
825
        index = self.make_index(1, key_elements=2, nodes=[
826
            (('name', 'fin1'), 'data', ([('ref', 'erence')], )),
827
            (('name', 'fin2'), 'beta', ([], )),
828
            (('ref', 'erence'), 'refdata', ([], ))])
829
        self.assertEqual(set([(index, ('name', 'fin1'), 'data', ((('ref', 'erence'),),)),
830
            (index, ('ref', 'erence'), 'refdata', ((), ))]),
831
            set(index.iter_entries_prefix([('name', 'fin1'), ('ref', 'erence')])))
832
        self.assertEqual(set([(index, ('name', 'fin1'), 'data', ((('ref', 'erence'),),)),
833
            (index, ('name', 'fin2'), 'beta', ((), ))]),
834
            set(index.iter_entries_prefix([('name', None)])))
835
836
837
class TestBTreeNodes(BTreeTestCase):
838
839
    def restore_parser(self):
3641.3.30 by John Arbash Meinel
Rename _parse_btree to _btree_serializer
840
        btree_index._btree_serializer = self.saved_parser
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
841
842
    def setUp(self):
843
        BTreeTestCase.setUp(self)
3641.3.30 by John Arbash Meinel
Rename _parse_btree to _btree_serializer
844
        self.saved_parser = btree_index._btree_serializer
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
845
        self.addCleanup(self.restore_parser)
3641.3.30 by John Arbash Meinel
Rename _parse_btree to _btree_serializer
846
        btree_index._btree_serializer = self.parse_btree
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
847
848
    def test_LeafNode_1_0(self):
849
        node_bytes = ("type=leaf\n"
850
            "0000000000000000000000000000000000000000\x00\x00value:0\n"
851
            "1111111111111111111111111111111111111111\x00\x00value:1\n"
852
            "2222222222222222222222222222222222222222\x00\x00value:2\n"
853
            "3333333333333333333333333333333333333333\x00\x00value:3\n"
854
            "4444444444444444444444444444444444444444\x00\x00value:4\n")
855
        node = btree_index._LeafNode(node_bytes, 1, 0)
856
        # We do direct access, or don't care about order, to leaf nodes most of
857
        # the time, so a dict is useful:
858
        self.assertEqual({
859
            ("0000000000000000000000000000000000000000",): ("value:0", ()),
860
            ("1111111111111111111111111111111111111111",): ("value:1", ()),
861
            ("2222222222222222222222222222222222222222",): ("value:2", ()),
862
            ("3333333333333333333333333333333333333333",): ("value:3", ()),
863
            ("4444444444444444444444444444444444444444",): ("value:4", ()),
864
            }, node.keys)
865
866
    def test_LeafNode_2_2(self):
867
        node_bytes = ("type=leaf\n"
868
            "00\x0000\x00\t00\x00ref00\x00value:0\n"
869
            "00\x0011\x0000\x00ref00\t00\x00ref00\r01\x00ref01\x00value:1\n"
870
            "11\x0033\x0011\x00ref22\t11\x00ref22\r11\x00ref22\x00value:3\n"
871
            "11\x0044\x00\t11\x00ref00\x00value:4\n"
872
            ""
873
            )
874
        node = btree_index._LeafNode(node_bytes, 2, 2)
875
        # We do direct access, or don't care about order, to leaf nodes most of
876
        # the time, so a dict is useful:
877
        self.assertEqual({
878
            ('00', '00'): ('value:0', ((), (('00', 'ref00'),))),
879
            ('00', '11'): ('value:1',
880
                ((('00', 'ref00'),), (('00', 'ref00'), ('01', 'ref01')))),
881
            ('11', '33'): ('value:3',
882
                ((('11', 'ref22'),), (('11', 'ref22'), ('11', 'ref22')))),
883
            ('11', '44'): ('value:4', ((), (('11', 'ref00'),)))
884
            }, node.keys)
885
886
    def test_InternalNode_1(self):
887
        node_bytes = ("type=internal\n"
888
            "offset=1\n"
889
            "0000000000000000000000000000000000000000\n"
890
            "1111111111111111111111111111111111111111\n"
891
            "2222222222222222222222222222222222222222\n"
892
            "3333333333333333333333333333333333333333\n"
893
            "4444444444444444444444444444444444444444\n"
894
            )
895
        node = btree_index._InternalNode(node_bytes)
896
        # We want to bisect to find the right children from this node, so a
897
        # vector is most useful.
898
        self.assertEqual([
899
            ("0000000000000000000000000000000000000000",),
900
            ("1111111111111111111111111111111111111111",),
901
            ("2222222222222222222222222222222222222222",),
902
            ("3333333333333333333333333333333333333333",),
903
            ("4444444444444444444444444444444444444444",),
904
            ], node.keys)
905
        self.assertEqual(1, node.offset)
906
907
    def test_LeafNode_2_2(self):
908
        node_bytes = ("type=leaf\n"
909
            "00\x0000\x00\t00\x00ref00\x00value:0\n"
910
            "00\x0011\x0000\x00ref00\t00\x00ref00\r01\x00ref01\x00value:1\n"
911
            "11\x0033\x0011\x00ref22\t11\x00ref22\r11\x00ref22\x00value:3\n"
912
            "11\x0044\x00\t11\x00ref00\x00value:4\n"
913
            ""
914
            )
915
        node = btree_index._LeafNode(node_bytes, 2, 2)
916
        # We do direct access, or don't care about order, to leaf nodes most of
917
        # the time, so a dict is useful:
918
        self.assertEqual({
919
            ('00', '00'): ('value:0', ((), (('00', 'ref00'),))),
920
            ('00', '11'): ('value:1',
921
                ((('00', 'ref00'),), (('00', 'ref00'), ('01', 'ref01')))),
922
            ('11', '33'): ('value:3',
923
                ((('11', 'ref22'),), (('11', 'ref22'), ('11', 'ref22')))),
924
            ('11', '44'): ('value:4', ((), (('11', 'ref00'),)))
925
            }, node.keys)
926
3641.3.19 by John Arbash Meinel
The flatten code now handles the no-ref-list case.
927
    def assertFlattened(self, expected, key, value, refs):
3641.3.18 by John Arbash Meinel
Start working on a compiled function for transforming
928
        flat_key, flat_line = self.parse_btree._flatten_node(
3641.3.19 by John Arbash Meinel
The flatten code now handles the no-ref-list case.
929
            (None, key, value, refs), bool(refs))
3641.3.18 by John Arbash Meinel
Start working on a compiled function for transforming
930
        self.assertEqual('\x00'.join(key), flat_key)
931
        self.assertEqual(expected, flat_line)
932
3641.3.19 by John Arbash Meinel
The flatten code now handles the no-ref-list case.
933
    def test__flatten_node(self):
934
        self.assertFlattened('key\0\0value\n', ('key',), 'value', [])
935
        self.assertFlattened('key\0tuple\0\0value str\n',
936
                             ('key', 'tuple'), 'value str', [])
937
        self.assertFlattened('key\0tuple\0triple\0\0value str\n',
938
                             ('key', 'tuple', 'triple'), 'value str', [])
939
        self.assertFlattened('k\0t\0s\0ref\0value str\n',
940
                             ('k', 't', 's'), 'value str', [[('ref',)]])
941
        self.assertFlattened('key\0tuple\0ref\0key\0value str\n',
942
                             ('key', 'tuple'), 'value str', [[('ref', 'key')]])
943
        self.assertFlattened("00\x0000\x00\t00\x00ref00\x00value:0\n",
944
            ('00', '00'), 'value:0', ((), (('00', 'ref00'),)))
945
        self.assertFlattened(
946
            "00\x0011\x0000\x00ref00\t00\x00ref00\r01\x00ref01\x00value:1\n",
947
            ('00', '11'), 'value:1',
948
                ((('00', 'ref00'),), (('00', 'ref00'), ('01', 'ref01'))))
949
        self.assertFlattened(
950
            "11\x0033\x0011\x00ref22\t11\x00ref22\r11\x00ref22\x00value:3\n",
951
            ('11', '33'), 'value:3',
952
                ((('11', 'ref22'),), (('11', 'ref22'), ('11', 'ref22'))))
953
        self.assertFlattened(
954
            "11\x0044\x00\t11\x00ref00\x00value:4\n",
955
            ('11', '44'), 'value:4', ((), (('11', 'ref00'),)))
3641.3.18 by John Arbash Meinel
Start working on a compiled function for transforming
956
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
957
958
class TestCompiledBtree(tests.TestCase):
959
960
    def test_exists(self):
961
        # This is just to let the user know if they don't have the feature
962
        # available
963
        self.requireFeature(CompiledBtreeParserFeature)
964
965
966
class TestMultiBisectRight(tests.TestCase):
967
968
    def assertMultiBisectRight(self, offsets, search_keys, fixed_keys):
969
        self.assertEqual(offsets,
970
                         btree_index.BTreeGraphIndex._multi_bisect_right(
971
                            search_keys, fixed_keys))
972
973
    def test_after(self):
974
        self.assertMultiBisectRight([(1, ['b'])], ['b'], ['a'])
975
        self.assertMultiBisectRight([(3, ['e', 'f', 'g'])],
976
                                    ['e', 'f', 'g'], ['a', 'b', 'c'])
977
978
    def test_before(self):
979
        self.assertMultiBisectRight([(0, ['a'])], ['a'], ['b'])
980
        self.assertMultiBisectRight([(0, ['a', 'b', 'c', 'd'])],
981
                                    ['a', 'b', 'c', 'd'], ['e', 'f', 'g'])
982
983
    def test_exact(self):
984
        self.assertMultiBisectRight([(1, ['a'])], ['a'], ['a'])
985
        self.assertMultiBisectRight([(1, ['a']), (2, ['b'])], ['a', 'b'], ['a', 'b'])
986
        self.assertMultiBisectRight([(1, ['a']), (3, ['c'])],
987
                                    ['a', 'c'], ['a', 'b', 'c'])
988
989
    def test_inbetween(self):
990
        self.assertMultiBisectRight([(1, ['b'])], ['b'], ['a', 'c'])
991
        self.assertMultiBisectRight([(1, ['b', 'c', 'd']), (2, ['f', 'g'])],
992
                                    ['b', 'c', 'd', 'f', 'g'], ['a', 'e', 'h'])
993
994
    def test_mixed(self):
995
        self.assertMultiBisectRight([(0, ['a', 'b']), (2, ['d', 'e']),
996
                                     (4, ['g', 'h'])],
997
                                    ['a', 'b', 'd', 'e', 'g', 'h'],
998
                                    ['c', 'd', 'f', 'g'])