/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_btree_index.py

  • Committer: mernst at mit
  • Date: 2008-10-16 10:57:16 UTC
  • mto: This revision was merged to the branch mainline in revision 3799.
  • Revision ID: mernst@csail.mit.edu-20081016105716-v8x8n5t2pf7f6uds
Improved documentation of stacked and lightweight branches

These patches improve the User Guide's documentation of stacked and
lightweight branches.

Section "1.2.6 Putting the concepts together" should mention stacked
branches and the difference between them and lightweight branches.  It
should also contain links to further details of the common scenarios.

Section "5.3.4 Getting a lightweight checkout" should mention stacked
branches as an option, and should link to all the options, not just some of
them.  It should also clarify that lightweight only applies to checkouts,
not to arbitrary branches.

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.assertIs(None, 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.assertIs(None, 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.assertIs(None, 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.assertIs(None, 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.assertIs(None, 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(1, len(builder._nodes))
 
447
        self.assertIs(None, 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.assertIs(None, 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
        old = dict(builder._get_nodes_by_key()) #Build up the nodes by key dict
 
456
        builder.add_node(*nodes[2])
 
457
        self.assertEqual(1, len(builder._nodes))
 
458
        self.assertEqual(1, len(builder._keys))
 
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)
 
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))
 
467
        self.assertIs(None, builder._nodes_by_key)
 
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))
 
476
        self.assertIs(None, builder._nodes_by_key)
 
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
 
506
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
 
507
            set(builder.iter_entries([nodes[12][0], nodes[11][0]])))
 
508
        self.assertEqual(13, builder.key_count())
 
509
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
 
510
            set(builder.iter_entries_prefix([nodes[12][0], nodes[11][0]])))
 
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)
 
606
        self.assertEqual(1199, size)
 
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())
 
615
        self.assertEqual(17692, size)
 
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)
 
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)
 
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
 
648
        self.assertEqual(9339, size)
 
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),
 
655
            ('readv', 'index', [(4096, 4096), (8192, 1147)], False, None)],
 
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.
 
698
        self.shrink_page_size()
 
699
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
 
700
        # 20k nodes is enough to create a two internal nodes on the second
 
701
        # level, with a 2K page size
 
702
        nodes = self.make_nodes(10000, 2, 2)
 
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())
 
707
        self.assertEqual(1303220, size, 'number of expected bytes in the'
 
708
                                        ' output changed')
 
709
        page_size = btree_index._PAGE_SIZE
 
710
        del builder
 
711
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
 
712
        del transport._activity[:]
 
713
        self.assertEqual([], transport._activity)
 
714
        found_nodes = self.time(list, index.iter_all_entries())
 
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
 
722
        self.assertEqual(20000, len(found_nodes))
 
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])
 
730
        self.assertEqual(1303220, size)
 
731
        # The start of the leaves
 
732
        first_byte = index._row_offsets[-2] * page_size
 
733
        readv_request = []
 
734
        for offset in range(first_byte, size, page_size):
 
735
            readv_request.append((offset, page_size))
 
736
        # The last page is truncated
 
737
        readv_request[-1] = (readv_request[-1][0], 1303220 % page_size)
 
738
        expected = [('readv', 'index', [(0, page_size)], False, None),
 
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),
 
778
             ('readv',  'index', [(8192, 4096), ], False, None)],
 
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):
 
840
        btree_index._btree_serializer = self.saved_parser
 
841
 
 
842
    def setUp(self):
 
843
        BTreeTestCase.setUp(self)
 
844
        self.saved_parser = btree_index._btree_serializer
 
845
        self.addCleanup(self.restore_parser)
 
846
        btree_index._btree_serializer = self.parse_btree
 
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
 
 
927
    def assertFlattened(self, expected, key, value, refs):
 
928
        flat_key, flat_line = self.parse_btree._flatten_node(
 
929
            (None, key, value, refs), bool(refs))
 
930
        self.assertEqual('\x00'.join(key), flat_key)
 
931
        self.assertEqual(expected, flat_line)
 
932
 
 
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'),)))
 
956
 
 
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'])