~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_btree_index.py

  • Committer: John Arbash Meinel
  • Date: 2008-09-02 17:52:00 UTC
  • mto: This revision was merged to the branch mainline in revision 3679.
  • Revision ID: john@arbash-meinel.com-20080902175200-nge9qgk0gklkd5ew
Move the point at which we 'buffer_all' if we've read >50% of the index.

We were doing it as soon as you entered 'iter_entries', but often you may already have enough
info to return results. And for small mostly local ops, we don't need to buffer all.
(This happens mostly with moderate size indexes, where the first read of the header
is enough to give you the data you need, but happens to be >50% of the whole file.)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
#
 
17
 
 
18
"""Tests for btree indices."""
 
19
 
 
20
import pprint
 
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()
 
43
    import bzrlib._btree_serializer_py as py_module
 
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?
 
48
        import bzrlib._btree_serializer_c as c_module
 
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:
 
57
            import bzrlib._btree_serializer_c
 
58
        except ImportError:
 
59
            return False
 
60
        return True
 
61
 
 
62
    def feature_name(self):
 
63
        return 'bzrlib._btree_serializer_c'
 
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 = ()
 
88
            for pos in xrange(count):
 
89
                # TODO: This creates odd keys. When count == 100,000, it
 
90
                #       creates a 240 byte key
 
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.
 
100
                        # It also ensures we have non homogeneous lists.
 
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
 
 
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
 
 
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(
 
134
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=0\n"
 
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(
 
145
            "B+Tree Graph Index 2\nnode_ref_lists=1\nkey_elements=2\nlen=0\n"
 
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(
 
160
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=5\n"
 
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(
 
184
            "B+Tree Graph Index 2\nnode_ref_lists=2\nkey_elements=2\nlen=10\n"
 
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)
 
207
        nodes = self.make_nodes(400, 1, 0)
 
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
 
214
        self.assertEqual(9283, len(content))
 
215
        self.assertEqual(
 
216
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=400\n"
 
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"
 
226
            ) + ("307" * 40) + "\n"
 
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)
 
232
        self.assertEqual(231, len(node.keys))
 
233
        self.assertEqual(sorted_node_keys[:231], sorted(node.keys))
 
234
        leaf2_bytes = zlib.decompress(leaf2)
 
235
        node = btree_index._LeafNode(leaf2_bytes, 1, 0)
 
236
        self.assertEqual(400 - 231, len(node.keys))
 
237
        self.assertEqual(sorted_node_keys[231:], sorted(node.keys))
 
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(
 
250
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=10\n"
 
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)
 
263
        nodes = self.make_nodes(400, 1, 0)
 
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
 
270
        self.assertEqual(9283, len(content))
 
271
        self.assertEqual(
 
272
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=400\n"
 
273
            "row_lengths=1,2\n",
 
274
            content[:77])
 
275
        # Check the last page is well formed
 
276
        leaf2 = content[8192:]
 
277
        leaf2_bytes = zlib.decompress(leaf2)
 
278
        node = btree_index._LeafNode(leaf2_bytes, 1, 0)
 
279
        self.assertEqual(400 - 231, len(node.keys))
 
280
        sorted_node_keys = sorted(node[0] for node in nodes)
 
281
        self.assertEqual(sorted_node_keys[231:], sorted(node.keys))
 
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.
 
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)
 
293
 
 
294
        for node in nodes:
 
295
            builder.add_node(*node)
 
296
        transport = get_transport('trace+' + self.get_url(''))
 
297
        size = transport.put_file('index', self.time(builder.finish))
 
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)
 
322
        nodes = self.make_nodes(100, 2, 2)
 
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
 
329
        self.assertEqual(12643, len(content))
 
330
        self.assertEqual(
 
331
            "B+Tree Graph Index 2\nnode_ref_lists=2\nkey_elements=2\nlen=200\n"
 
332
            "row_lengths=1,3\n",
 
333
            content[:77])
 
334
        root = content[77:4096]
 
335
        leaf1 = content[4096:8192]
 
336
        leaf2 = content[8192:12288]
 
337
        leaf3 = content[12288:]
 
338
        root_bytes = zlib.decompress(root)
 
339
        expected_root = (
 
340
            "type=internal\n"
 
341
            "offset=0\n"
 
342
            + ("0" * 40) + "\x00" + ("91" * 40) + "\n"
 
343
            + ("1" * 40) + "\x00" + ("81" * 40) + "\n"
 
344
            )
 
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))
 
357
        self.assertEqual({}, builder._nodes_by_key)
 
358
        builder.add_node(*nodes[1])
 
359
        self.assertEqual(0, len(builder._nodes))
 
360
        self.assertEqual(0, len(builder._keys))
 
361
        self.assertEqual({}, builder._nodes_by_key)
 
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))
 
368
        self.assertEqual({}, builder._nodes_by_key)
 
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))
 
373
        self.assertEqual({}, builder._nodes_by_key)
 
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))
 
382
        self.assertEqual({}, builder._nodes_by_key)
 
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))
 
446
        self.assertEqual(2, len(builder._nodes))
 
447
        self.assertNotEqual({}, builder._nodes_by_key)
 
448
        builder.add_node(*nodes[1])
 
449
        self.assertEqual(0, len(builder._keys))
 
450
        self.assertEqual(0, len(builder._nodes))
 
451
        self.assertEqual({}, builder._nodes_by_key)
 
452
        self.assertEqual(1, len(builder._backing_indices))
 
453
        self.assertEqual(2, builder._backing_indices[0].key_count())
 
454
        # now back to memory
 
455
        builder.add_node(*nodes[2])
 
456
        self.assertEqual(2, len(builder._nodes))
 
457
        self.assertEqual(1, len(builder._keys))
 
458
        self.assertNotEqual({}, builder._nodes_by_key)
 
459
        # And spills to a second backing index combing all
 
460
        builder.add_node(*nodes[3])
 
461
        self.assertEqual(0, len(builder._nodes))
 
462
        self.assertEqual(0, len(builder._keys))
 
463
        self.assertEqual({}, builder._nodes_by_key)
 
464
        self.assertEqual(2, len(builder._backing_indices))
 
465
        self.assertEqual(None, builder._backing_indices[0])
 
466
        self.assertEqual(4, builder._backing_indices[1].key_count())
 
467
        # The next spills to the 2-len slot
 
468
        builder.add_node(*nodes[4])
 
469
        builder.add_node(*nodes[5])
 
470
        self.assertEqual(0, len(builder._nodes))
 
471
        self.assertEqual(0, len(builder._keys))
 
472
        self.assertEqual({}, builder._nodes_by_key)
 
473
        self.assertEqual(2, len(builder._backing_indices))
 
474
        self.assertEqual(2, builder._backing_indices[0].key_count())
 
475
        self.assertEqual(4, builder._backing_indices[1].key_count())
 
476
        # Next spill combines
 
477
        builder.add_node(*nodes[6])
 
478
        builder.add_node(*nodes[7])
 
479
        self.assertEqual(3, len(builder._backing_indices))
 
480
        self.assertEqual(None, builder._backing_indices[0])
 
481
        self.assertEqual(None, builder._backing_indices[1])
 
482
        self.assertEqual(8, builder._backing_indices[2].key_count())
 
483
        # And so forth - counting up in binary.
 
484
        builder.add_node(*nodes[8])
 
485
        builder.add_node(*nodes[9])
 
486
        self.assertEqual(3, len(builder._backing_indices))
 
487
        self.assertEqual(2, builder._backing_indices[0].key_count())
 
488
        self.assertEqual(None, builder._backing_indices[1])
 
489
        self.assertEqual(8, builder._backing_indices[2].key_count())
 
490
        builder.add_node(*nodes[10])
 
491
        builder.add_node(*nodes[11])
 
492
        self.assertEqual(3, len(builder._backing_indices))
 
493
        self.assertEqual(None, builder._backing_indices[0])
 
494
        self.assertEqual(4, builder._backing_indices[1].key_count())
 
495
        self.assertEqual(8, builder._backing_indices[2].key_count())
 
496
        builder.add_node(*nodes[12])
 
497
        # Test that memory and disk are both used for query methods; and that
 
498
        # None is skipped over happily.
 
499
        self.assertEqual([(builder,) + node for node in sorted(nodes[:13])],
 
500
            list(builder.iter_all_entries()))
 
501
        # Two nodes - one memory one disk
 
502
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
 
503
            set(builder.iter_entries([nodes[12][0], nodes[11][0]])))
 
504
        self.assertEqual(13, builder.key_count())
 
505
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
 
506
            set(builder.iter_entries_prefix([nodes[12][0], nodes[11][0]])))
 
507
        builder.add_node(*nodes[13])
 
508
        self.assertEqual(3, len(builder._backing_indices))
 
509
        self.assertEqual(2, builder._backing_indices[0].key_count())
 
510
        self.assertEqual(4, builder._backing_indices[1].key_count())
 
511
        self.assertEqual(8, builder._backing_indices[2].key_count())
 
512
        builder.add_node(*nodes[14])
 
513
        builder.add_node(*nodes[15])
 
514
        self.assertEqual(4, len(builder._backing_indices))
 
515
        self.assertEqual(None, builder._backing_indices[0])
 
516
        self.assertEqual(None, builder._backing_indices[1])
 
517
        self.assertEqual(None, builder._backing_indices[2])
 
518
        self.assertEqual(16, builder._backing_indices[3].key_count())
 
519
        # Now finish, and check we got a correctly ordered tree
 
520
        transport = self.get_transport('')
 
521
        size = transport.put_file('index', builder.finish())
 
522
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
 
523
        nodes = list(index.iter_all_entries())
 
524
        self.assertEqual(sorted(nodes), nodes)
 
525
        self.assertEqual(16, len(nodes))
 
526
 
 
527
    def test_spill_index_duplicate_key_caught_on_finish(self):
 
528
        builder = btree_index.BTreeBuilder(key_elements=1, spill_at=2)
 
529
        nodes = [node[0:2] for node in self.make_nodes(16, 1, 0)]
 
530
        builder.add_node(*nodes[0])
 
531
        builder.add_node(*nodes[1])
 
532
        builder.add_node(*nodes[0])
 
533
        self.assertRaises(errors.BadIndexDuplicateKey, builder.finish)
 
534
 
 
535
 
 
536
class TestBTreeIndex(BTreeTestCase):
 
537
 
 
538
    def make_index(self, ref_lists=0, key_elements=1, nodes=[]):
 
539
        builder = btree_index.BTreeBuilder(reference_lists=ref_lists,
 
540
            key_elements=key_elements)
 
541
        for key, value, references in nodes:
 
542
            builder.add_node(key, value, references)
 
543
        stream = builder.finish()
 
544
        trans = get_transport('trace+' + self.get_url())
 
545
        size = trans.put_file('index', stream)
 
546
        return btree_index.BTreeGraphIndex(trans, 'index', size)
 
547
 
 
548
    def test_trivial_constructor(self):
 
549
        transport = get_transport('trace+' + self.get_url(''))
 
550
        index = btree_index.BTreeGraphIndex(transport, 'index', None)
 
551
        # Checks the page size at load, but that isn't logged yet.
 
552
        self.assertEqual([], transport._activity)
 
553
 
 
554
    def test_with_size_constructor(self):
 
555
        transport = get_transport('trace+' + self.get_url(''))
 
556
        index = btree_index.BTreeGraphIndex(transport, 'index', 1)
 
557
        # Checks the page size at load, but that isn't logged yet.
 
558
        self.assertEqual([], transport._activity)
 
559
 
 
560
    def test_empty_key_count_no_size(self):
 
561
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
 
562
        transport = get_transport('trace+' + self.get_url(''))
 
563
        transport.put_file('index', builder.finish())
 
564
        index = btree_index.BTreeGraphIndex(transport, 'index', None)
 
565
        del transport._activity[:]
 
566
        self.assertEqual([], transport._activity)
 
567
        self.assertEqual(0, index.key_count())
 
568
        # The entire index should have been requested (as we generally have the
 
569
        # size available, and doing many small readvs is inappropriate).
 
570
        # We can't tell how much was actually read here, but - check the code.
 
571
        self.assertEqual([('get', 'index'),
 
572
            ('readv', 'index', [(0, 72)], False, None)],
 
573
            transport._activity)
 
574
 
 
575
    def test_empty_key_count(self):
 
576
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
 
577
        transport = get_transport('trace+' + self.get_url(''))
 
578
        size = transport.put_file('index', builder.finish())
 
579
        self.assertEqual(72, size)
 
580
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
 
581
        del transport._activity[:]
 
582
        self.assertEqual([], transport._activity)
 
583
        self.assertEqual(0, index.key_count())
 
584
        # The entire index should have been read, as 4K > size
 
585
        self.assertEqual([('readv', 'index', [(0, 72)], False, None)],
 
586
            transport._activity)
 
587
 
 
588
    def test_non_empty_key_count_2_2(self):
 
589
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
 
590
        nodes = self.make_nodes(35, 2, 2)
 
591
        for node in nodes:
 
592
            builder.add_node(*node)
 
593
        transport = get_transport('trace+' + self.get_url(''))
 
594
        size = transport.put_file('index', builder.finish())
 
595
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
 
596
        del transport._activity[:]
 
597
        self.assertEqual([], transport._activity)
 
598
        self.assertEqual(70, index.key_count())
 
599
        # The entire index should have been read, as it is one page long.
 
600
        self.assertEqual([('readv', 'index', [(0, size)], False, None)],
 
601
            transport._activity)
 
602
        self.assertEqual(1199, size)
 
603
 
 
604
    def test_2_levels_key_count_2_2(self):
 
605
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
 
606
        nodes = self.make_nodes(160, 2, 2)
 
607
        for node in nodes:
 
608
            builder.add_node(*node)
 
609
        transport = get_transport('trace+' + self.get_url(''))
 
610
        size = transport.put_file('index', builder.finish())
 
611
        self.assertEqual(17692, size)
 
612
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
 
613
        del transport._activity[:]
 
614
        self.assertEqual([], transport._activity)
 
615
        self.assertEqual(320, index.key_count())
 
616
        # The entire index should not have been read.
 
617
        self.assertEqual([('readv', 'index', [(0, 4096)], False, None)],
 
618
            transport._activity)
 
619
 
 
620
    def test_validate_one_page(self):
 
621
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
 
622
        nodes = self.make_nodes(45, 2, 2)
 
623
        for node in nodes:
 
624
            builder.add_node(*node)
 
625
        transport = get_transport('trace+' + self.get_url(''))
 
626
        size = transport.put_file('index', builder.finish())
 
627
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
 
628
        del transport._activity[:]
 
629
        self.assertEqual([], transport._activity)
 
630
        index.validate()
 
631
        # The entire index should have been read linearly.
 
632
        self.assertEqual([('readv', 'index', [(0, size)], False, None)],
 
633
            transport._activity)
 
634
        self.assertEqual(1514, size)
 
635
 
 
636
    def test_validate_two_pages(self):
 
637
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
 
638
        nodes = self.make_nodes(80, 2, 2)
 
639
        for node in nodes:
 
640
            builder.add_node(*node)
 
641
        transport = get_transport('trace+' + self.get_url(''))
 
642
        size = transport.put_file('index', builder.finish())
 
643
        # Root page, 2 leaf pages
 
644
        self.assertEqual(9339, size)
 
645
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
 
646
        del transport._activity[:]
 
647
        self.assertEqual([], transport._activity)
 
648
        index.validate()
 
649
        # The entire index should have been read linearly.
 
650
        self.assertEqual([('readv', 'index', [(0, 4096)], False, None),
 
651
            ('readv', 'index', [(4096, 4096), (8192, 1147)], False, None)],
 
652
            transport._activity)
 
653
        # XXX: TODO: write some badly-ordered nodes, and some pointers-to-wrong
 
654
        # node and make validate find them.
 
655
 
 
656
    def test_eq_ne(self):
 
657
        # two indices are equal when constructed with the same parameters:
 
658
        transport1 = get_transport('trace+' + self.get_url(''))
 
659
        transport2 = get_transport(self.get_url(''))
 
660
        self.assertTrue(
 
661
            btree_index.BTreeGraphIndex(transport1, 'index', None) ==
 
662
            btree_index.BTreeGraphIndex(transport1, 'index', None))
 
663
        self.assertTrue(
 
664
            btree_index.BTreeGraphIndex(transport1, 'index', 20) ==
 
665
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
 
666
        self.assertFalse(
 
667
            btree_index.BTreeGraphIndex(transport1, 'index', 20) ==
 
668
            btree_index.BTreeGraphIndex(transport2, 'index', 20))
 
669
        self.assertFalse(
 
670
            btree_index.BTreeGraphIndex(transport1, 'inde1', 20) ==
 
671
            btree_index.BTreeGraphIndex(transport1, 'inde2', 20))
 
672
        self.assertFalse(
 
673
            btree_index.BTreeGraphIndex(transport1, 'index', 10) ==
 
674
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
 
675
        self.assertFalse(
 
676
            btree_index.BTreeGraphIndex(transport1, 'index', None) !=
 
677
            btree_index.BTreeGraphIndex(transport1, 'index', None))
 
678
        self.assertFalse(
 
679
            btree_index.BTreeGraphIndex(transport1, 'index', 20) !=
 
680
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
 
681
        self.assertTrue(
 
682
            btree_index.BTreeGraphIndex(transport1, 'index', 20) !=
 
683
            btree_index.BTreeGraphIndex(transport2, 'index', 20))
 
684
        self.assertTrue(
 
685
            btree_index.BTreeGraphIndex(transport1, 'inde1', 20) !=
 
686
            btree_index.BTreeGraphIndex(transport1, 'inde2', 20))
 
687
        self.assertTrue(
 
688
            btree_index.BTreeGraphIndex(transport1, 'index', 10) !=
 
689
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
 
690
 
 
691
    def test_iter_all_entries_reads(self):
 
692
        # iterating all entries reads the header, then does a linear
 
693
        # read.
 
694
        self.shrink_page_size()
 
695
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
 
696
        # 20k nodes is enough to create a two internal nodes on the second
 
697
        # level, with a 2K page size
 
698
        nodes = self.make_nodes(10000, 2, 2)
 
699
        for node in nodes:
 
700
            builder.add_node(*node)
 
701
        transport = get_transport('trace+' + self.get_url(''))
 
702
        size = transport.put_file('index', builder.finish())
 
703
        self.assertEqual(1303220, size, 'number of expected bytes in the'
 
704
                                        ' output changed')
 
705
        page_size = btree_index._PAGE_SIZE
 
706
        del builder
 
707
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
 
708
        del transport._activity[:]
 
709
        self.assertEqual([], transport._activity)
 
710
        found_nodes = self.time(list, index.iter_all_entries())
 
711
        bare_nodes = []
 
712
        for node in found_nodes:
 
713
            self.assertTrue(node[0] is index)
 
714
            bare_nodes.append(node[1:])
 
715
        self.assertEqual(3, len(index._row_lengths),
 
716
            "Not enough rows: %r" % index._row_lengths)
 
717
        # Should be as long as the nodes we supplied
 
718
        self.assertEqual(20000, len(found_nodes))
 
719
        # Should have the same content
 
720
        self.assertEqual(set(nodes), set(bare_nodes))
 
721
        # Should have done linear scan IO up the index, ignoring
 
722
        # the internal nodes:
 
723
        # The entire index should have been read
 
724
        total_pages = sum(index._row_lengths)
 
725
        self.assertEqual(total_pages, index._row_offsets[-1])
 
726
        self.assertEqual(1303220, size)
 
727
        # The start of the leaves
 
728
        first_byte = index._row_offsets[-2] * page_size
 
729
        readv_request = []
 
730
        for offset in range(first_byte, size, page_size):
 
731
            readv_request.append((offset, page_size))
 
732
        # The last page is truncated
 
733
        readv_request[-1] = (readv_request[-1][0], 1303220 % page_size)
 
734
        expected = [('readv', 'index', [(0, page_size)], False, None),
 
735
             ('readv',  'index', readv_request, False, None)]
 
736
        if expected != transport._activity:
 
737
            self.assertEqualDiff(pprint.pformat(expected),
 
738
                                 pprint.pformat(transport._activity))
 
739
 
 
740
    def _test_iter_entries_references_resolved(self):
 
741
        index = self.make_index(1, nodes=[
 
742
            (('name', ), 'data', ([('ref', ), ('ref', )], )),
 
743
            (('ref', ), 'refdata', ([], ))])
 
744
        self.assertEqual(set([(index, ('name', ), 'data', ((('ref',),('ref',)),)),
 
745
            (index, ('ref', ), 'refdata', ((), ))]),
 
746
            set(index.iter_entries([('name',), ('ref',)])))
 
747
 
 
748
    def test_iter_entries_references_2_refs_resolved(self):
 
749
        # iterating some entries reads just the pages needed. For now, to
 
750
        # get it working and start measuring, only 4K pages are read.
 
751
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
 
752
        # 80 nodes is enough to create a two-level index.
 
753
        nodes = self.make_nodes(160, 2, 2)
 
754
        for node in nodes:
 
755
            builder.add_node(*node)
 
756
        transport = get_transport('trace+' + self.get_url(''))
 
757
        size = transport.put_file('index', builder.finish())
 
758
        del builder
 
759
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
 
760
        del transport._activity[:]
 
761
        self.assertEqual([], transport._activity)
 
762
        # search for one key
 
763
        found_nodes = list(index.iter_entries([nodes[30][0]]))
 
764
        bare_nodes = []
 
765
        for node in found_nodes:
 
766
            self.assertTrue(node[0] is index)
 
767
            bare_nodes.append(node[1:])
 
768
        # Should be as long as the nodes we supplied
 
769
        self.assertEqual(1, len(found_nodes))
 
770
        # Should have the same content
 
771
        self.assertEqual(nodes[30], bare_nodes[0])
 
772
        # Should have read the root node, then one leaf page:
 
773
        self.assertEqual([('readv', 'index', [(0, 4096)], False, None),
 
774
             ('readv',  'index', [(8192, 4096), ], False, None)],
 
775
            transport._activity)
 
776
 
 
777
    def test_iter_key_prefix_1_element_key_None(self):
 
778
        index = self.make_index()
 
779
        self.assertRaises(errors.BadIndexKey, list,
 
780
            index.iter_entries_prefix([(None, )]))
 
781
 
 
782
    def test_iter_key_prefix_wrong_length(self):
 
783
        index = self.make_index()
 
784
        self.assertRaises(errors.BadIndexKey, list,
 
785
            index.iter_entries_prefix([('foo', None)]))
 
786
        index = self.make_index(key_elements=2)
 
787
        self.assertRaises(errors.BadIndexKey, list,
 
788
            index.iter_entries_prefix([('foo', )]))
 
789
        self.assertRaises(errors.BadIndexKey, list,
 
790
            index.iter_entries_prefix([('foo', None, None)]))
 
791
 
 
792
    def test_iter_key_prefix_1_key_element_no_refs(self):
 
793
        index = self.make_index( nodes=[
 
794
            (('name', ), 'data', ()),
 
795
            (('ref', ), 'refdata', ())])
 
796
        self.assertEqual(set([(index, ('name', ), 'data'),
 
797
            (index, ('ref', ), 'refdata')]),
 
798
            set(index.iter_entries_prefix([('name', ), ('ref', )])))
 
799
 
 
800
    def test_iter_key_prefix_1_key_element_refs(self):
 
801
        index = self.make_index(1, nodes=[
 
802
            (('name', ), 'data', ([('ref', )], )),
 
803
            (('ref', ), 'refdata', ([], ))])
 
804
        self.assertEqual(set([(index, ('name', ), 'data', ((('ref',),),)),
 
805
            (index, ('ref', ), 'refdata', ((), ))]),
 
806
            set(index.iter_entries_prefix([('name', ), ('ref', )])))
 
807
 
 
808
    def test_iter_key_prefix_2_key_element_no_refs(self):
 
809
        index = self.make_index(key_elements=2, nodes=[
 
810
            (('name', 'fin1'), 'data', ()),
 
811
            (('name', 'fin2'), 'beta', ()),
 
812
            (('ref', 'erence'), 'refdata', ())])
 
813
        self.assertEqual(set([(index, ('name', 'fin1'), 'data'),
 
814
            (index, ('ref', 'erence'), 'refdata')]),
 
815
            set(index.iter_entries_prefix([('name', 'fin1'), ('ref', 'erence')])))
 
816
        self.assertEqual(set([(index, ('name', 'fin1'), 'data'),
 
817
            (index, ('name', 'fin2'), 'beta')]),
 
818
            set(index.iter_entries_prefix([('name', None)])))
 
819
 
 
820
    def test_iter_key_prefix_2_key_element_refs(self):
 
821
        index = self.make_index(1, key_elements=2, nodes=[
 
822
            (('name', 'fin1'), 'data', ([('ref', 'erence')], )),
 
823
            (('name', 'fin2'), 'beta', ([], )),
 
824
            (('ref', 'erence'), 'refdata', ([], ))])
 
825
        self.assertEqual(set([(index, ('name', 'fin1'), 'data', ((('ref', 'erence'),),)),
 
826
            (index, ('ref', 'erence'), 'refdata', ((), ))]),
 
827
            set(index.iter_entries_prefix([('name', 'fin1'), ('ref', 'erence')])))
 
828
        self.assertEqual(set([(index, ('name', 'fin1'), 'data', ((('ref', 'erence'),),)),
 
829
            (index, ('name', 'fin2'), 'beta', ((), ))]),
 
830
            set(index.iter_entries_prefix([('name', None)])))
 
831
 
 
832
 
 
833
class TestBTreeNodes(BTreeTestCase):
 
834
 
 
835
    def restore_parser(self):
 
836
        btree_index._btree_serializer = self.saved_parser
 
837
 
 
838
    def setUp(self):
 
839
        BTreeTestCase.setUp(self)
 
840
        self.saved_parser = btree_index._btree_serializer
 
841
        self.addCleanup(self.restore_parser)
 
842
        btree_index._btree_serializer = self.parse_btree
 
843
 
 
844
    def test_LeafNode_1_0(self):
 
845
        node_bytes = ("type=leaf\n"
 
846
            "0000000000000000000000000000000000000000\x00\x00value:0\n"
 
847
            "1111111111111111111111111111111111111111\x00\x00value:1\n"
 
848
            "2222222222222222222222222222222222222222\x00\x00value:2\n"
 
849
            "3333333333333333333333333333333333333333\x00\x00value:3\n"
 
850
            "4444444444444444444444444444444444444444\x00\x00value:4\n")
 
851
        node = btree_index._LeafNode(node_bytes, 1, 0)
 
852
        # We do direct access, or don't care about order, to leaf nodes most of
 
853
        # the time, so a dict is useful:
 
854
        self.assertEqual({
 
855
            ("0000000000000000000000000000000000000000",): ("value:0", ()),
 
856
            ("1111111111111111111111111111111111111111",): ("value:1", ()),
 
857
            ("2222222222222222222222222222222222222222",): ("value:2", ()),
 
858
            ("3333333333333333333333333333333333333333",): ("value:3", ()),
 
859
            ("4444444444444444444444444444444444444444",): ("value:4", ()),
 
860
            }, node.keys)
 
861
 
 
862
    def test_LeafNode_2_2(self):
 
863
        node_bytes = ("type=leaf\n"
 
864
            "00\x0000\x00\t00\x00ref00\x00value:0\n"
 
865
            "00\x0011\x0000\x00ref00\t00\x00ref00\r01\x00ref01\x00value:1\n"
 
866
            "11\x0033\x0011\x00ref22\t11\x00ref22\r11\x00ref22\x00value:3\n"
 
867
            "11\x0044\x00\t11\x00ref00\x00value:4\n"
 
868
            ""
 
869
            )
 
870
        node = btree_index._LeafNode(node_bytes, 2, 2)
 
871
        # We do direct access, or don't care about order, to leaf nodes most of
 
872
        # the time, so a dict is useful:
 
873
        self.assertEqual({
 
874
            ('00', '00'): ('value:0', ((), (('00', 'ref00'),))),
 
875
            ('00', '11'): ('value:1',
 
876
                ((('00', 'ref00'),), (('00', 'ref00'), ('01', 'ref01')))),
 
877
            ('11', '33'): ('value:3',
 
878
                ((('11', 'ref22'),), (('11', 'ref22'), ('11', 'ref22')))),
 
879
            ('11', '44'): ('value:4', ((), (('11', 'ref00'),)))
 
880
            }, node.keys)
 
881
 
 
882
    def test_InternalNode_1(self):
 
883
        node_bytes = ("type=internal\n"
 
884
            "offset=1\n"
 
885
            "0000000000000000000000000000000000000000\n"
 
886
            "1111111111111111111111111111111111111111\n"
 
887
            "2222222222222222222222222222222222222222\n"
 
888
            "3333333333333333333333333333333333333333\n"
 
889
            "4444444444444444444444444444444444444444\n"
 
890
            )
 
891
        node = btree_index._InternalNode(node_bytes)
 
892
        # We want to bisect to find the right children from this node, so a
 
893
        # vector is most useful.
 
894
        self.assertEqual([
 
895
            ("0000000000000000000000000000000000000000",),
 
896
            ("1111111111111111111111111111111111111111",),
 
897
            ("2222222222222222222222222222222222222222",),
 
898
            ("3333333333333333333333333333333333333333",),
 
899
            ("4444444444444444444444444444444444444444",),
 
900
            ], node.keys)
 
901
        self.assertEqual(1, node.offset)
 
902
 
 
903
    def test_LeafNode_2_2(self):
 
904
        node_bytes = ("type=leaf\n"
 
905
            "00\x0000\x00\t00\x00ref00\x00value:0\n"
 
906
            "00\x0011\x0000\x00ref00\t00\x00ref00\r01\x00ref01\x00value:1\n"
 
907
            "11\x0033\x0011\x00ref22\t11\x00ref22\r11\x00ref22\x00value:3\n"
 
908
            "11\x0044\x00\t11\x00ref00\x00value:4\n"
 
909
            ""
 
910
            )
 
911
        node = btree_index._LeafNode(node_bytes, 2, 2)
 
912
        # We do direct access, or don't care about order, to leaf nodes most of
 
913
        # the time, so a dict is useful:
 
914
        self.assertEqual({
 
915
            ('00', '00'): ('value:0', ((), (('00', 'ref00'),))),
 
916
            ('00', '11'): ('value:1',
 
917
                ((('00', 'ref00'),), (('00', 'ref00'), ('01', 'ref01')))),
 
918
            ('11', '33'): ('value:3',
 
919
                ((('11', 'ref22'),), (('11', 'ref22'), ('11', 'ref22')))),
 
920
            ('11', '44'): ('value:4', ((), (('11', 'ref00'),)))
 
921
            }, node.keys)
 
922
 
 
923
    def assertFlattened(self, expected, key, value, refs):
 
924
        flat_key, flat_line = self.parse_btree._flatten_node(
 
925
            (None, key, value, refs), bool(refs))
 
926
        self.assertEqual('\x00'.join(key), flat_key)
 
927
        self.assertEqual(expected, flat_line)
 
928
 
 
929
    def test__flatten_node(self):
 
930
        self.assertFlattened('key\0\0value\n', ('key',), 'value', [])
 
931
        self.assertFlattened('key\0tuple\0\0value str\n',
 
932
                             ('key', 'tuple'), 'value str', [])
 
933
        self.assertFlattened('key\0tuple\0triple\0\0value str\n',
 
934
                             ('key', 'tuple', 'triple'), 'value str', [])
 
935
        self.assertFlattened('k\0t\0s\0ref\0value str\n',
 
936
                             ('k', 't', 's'), 'value str', [[('ref',)]])
 
937
        self.assertFlattened('key\0tuple\0ref\0key\0value str\n',
 
938
                             ('key', 'tuple'), 'value str', [[('ref', 'key')]])
 
939
        self.assertFlattened("00\x0000\x00\t00\x00ref00\x00value:0\n",
 
940
            ('00', '00'), 'value:0', ((), (('00', 'ref00'),)))
 
941
        self.assertFlattened(
 
942
            "00\x0011\x0000\x00ref00\t00\x00ref00\r01\x00ref01\x00value:1\n",
 
943
            ('00', '11'), 'value:1',
 
944
                ((('00', 'ref00'),), (('00', 'ref00'), ('01', 'ref01'))))
 
945
        self.assertFlattened(
 
946
            "11\x0033\x0011\x00ref22\t11\x00ref22\r11\x00ref22\x00value:3\n",
 
947
            ('11', '33'), 'value:3',
 
948
                ((('11', 'ref22'),), (('11', 'ref22'), ('11', 'ref22'))))
 
949
        self.assertFlattened(
 
950
            "11\x0044\x00\t11\x00ref00\x00value:4\n",
 
951
            ('11', '44'), 'value:4', ((), (('11', 'ref00'),)))
 
952
 
 
953
 
 
954
class TestCompiledBtree(tests.TestCase):
 
955
 
 
956
    def test_exists(self):
 
957
        # This is just to let the user know if they don't have the feature
 
958
        # available
 
959
        self.requireFeature(CompiledBtreeParserFeature)
 
960
 
 
961
 
 
962
class TestMultiBisectRight(tests.TestCase):
 
963
 
 
964
    def assertMultiBisectRight(self, offsets, search_keys, fixed_keys):
 
965
        self.assertEqual(offsets,
 
966
                         btree_index.BTreeGraphIndex._multi_bisect_right(
 
967
                            search_keys, fixed_keys))
 
968
 
 
969
    def test_after(self):
 
970
        self.assertMultiBisectRight([(1, ['b'])], ['b'], ['a'])
 
971
        self.assertMultiBisectRight([(3, ['e', 'f', 'g'])],
 
972
                                    ['e', 'f', 'g'], ['a', 'b', 'c'])
 
973
 
 
974
    def test_before(self):
 
975
        self.assertMultiBisectRight([(0, ['a'])], ['a'], ['b'])
 
976
        self.assertMultiBisectRight([(0, ['a', 'b', 'c', 'd'])],
 
977
                                    ['a', 'b', 'c', 'd'], ['e', 'f', 'g'])
 
978
 
 
979
    def test_exact(self):
 
980
        self.assertMultiBisectRight([(1, ['a'])], ['a'], ['a'])
 
981
        self.assertMultiBisectRight([(1, ['a']), (2, ['b'])], ['a', 'b'], ['a', 'b'])
 
982
        self.assertMultiBisectRight([(1, ['a']), (3, ['c'])],
 
983
                                    ['a', 'c'], ['a', 'b', 'c'])
 
984
 
 
985
    def test_inbetween(self):
 
986
        self.assertMultiBisectRight([(1, ['b'])], ['b'], ['a', 'c'])
 
987
        self.assertMultiBisectRight([(1, ['b', 'c', 'd']), (2, ['f', 'g'])],
 
988
                                    ['b', 'c', 'd', 'f', 'g'], ['a', 'e', 'h'])
 
989
 
 
990
    def test_mixed(self):
 
991
        self.assertMultiBisectRight([(0, ['a', 'b']), (2, ['d', 'e']),
 
992
                                     (4, ['g', 'h'])],
 
993
                                    ['a', 'b', 'd', 'e', 'g', 'h'],
 
994
                                    ['c', 'd', 'f', 'g'])