1
# Copyright (C) 2008 Canonical Limited.
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License version 2 as published
5
# by the Free Software Foundation.
7
# This program is distributed in the hope that it will be useful,
8
# but WITHOUT ANY WARRANTY; without even the implied warranty of
9
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
# GNU General Public License for more details.
12
# You should have received a copy of the GNU General Public License
13
# along with this program; if not, write to the Free Software
14
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for btree indices."""
27
from bzrlib.tests import (
28
TestCaseWithTransport,
32
split_suite_by_condition,
34
from bzrlib.transport import get_transport
37
def load_tests(standard_tests, module, loader):
38
# parameterise the TestBTreeNodes tests
39
node_tests, others = split_suite_by_condition(standard_tests,
40
condition_isinstance(TestBTreeNodes))
41
applier = TestScenarioApplier()
42
import bzrlib._parse_btree_py as py_module
43
applier.scenarios = [('python', {'parse_btree': py_module})]
44
if CompiledBtreeParserFeature.available():
45
# Is there a way to do this that gets missing feature failures rather
46
# than no indication to the user?
47
import bzrlib._parse_btree_c as c_module
48
applier.scenarios.append(('C', {'parse_btree': c_module}))
49
adapt_tests(node_tests, applier, others)
53
class _CompiledBtreeParserFeature(tests.Feature):
56
import bzrlib._parse_btree_c
61
def feature_name(self):
62
return 'bzrlib._parse_btree_c'
64
CompiledBtreeParserFeature = _CompiledBtreeParserFeature()
67
class BTreeTestCase(TestCaseWithTransport):
68
# test names here are suffixed by the key length and reference list count
72
TestCaseWithTransport.setUp(self)
73
self._original_header = btree_index._RESERVED_HEADER_BYTES
75
btree_index._RESERVED_HEADER_BYTES = self._original_header
76
self.addCleanup(restore)
77
btree_index._RESERVED_HEADER_BYTES = 100
79
def make_nodes(self, count, key_elements, reference_lists):
80
"""Generate count*key_elements sample nodes."""
82
for prefix_pos in range(key_elements):
84
prefix = (str(prefix_pos) * 40,)
87
for pos in range(count):
88
key = prefix + (str(pos) * 40,)
89
value = "value:%s" % pos
91
# generate some references
93
for list_pos in range(reference_lists):
94
# as many keys in each list as its index + the key depth
95
# mod 2 - this generates both 0 length lists and
96
# ones slightly longer than the number of lists.
97
# It alsu ensures we have non homogeneous lists.
99
for ref_pos in range(list_pos + pos % 2):
101
# refer to a nearby key
102
refs[-1].append(prefix + ("ref" + str(pos - 1) * 40,))
104
# serial of this ref in the ref list
105
refs[-1].append(prefix + ("ref" + str(ref_pos) * 40,))
106
refs[-1] = tuple(refs[-1])
110
keys.append((key, value, refs))
114
class TestBTreeBuilder(BTreeTestCase):
116
def test_empty_1_0(self):
117
builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
118
# NamedTemporaryFile dies on builder.finish().read(). weird.
119
temp_file = builder.finish()
120
content = temp_file.read()
123
"B+Tree Graph Index 1\nnode_ref_lists=0\nkey_elements=1\nlen=0\n"
127
def test_empty_2_1(self):
128
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=1)
129
# NamedTemporaryFile dies on builder.finish().read(). weird.
130
temp_file = builder.finish()
131
content = temp_file.read()
134
"B+Tree Graph Index 1\nnode_ref_lists=1\nkey_elements=2\nlen=0\n"
138
def test_root_leaf_1_0(self):
139
builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
140
nodes = self.make_nodes(5, 1, 0)
142
builder.add_node(*node)
143
# NamedTemporaryFile dies on builder.finish().read(). weird.
144
temp_file = builder.finish()
145
content = temp_file.read()
147
self.assertEqual(158, len(content))
149
"B+Tree Graph Index 1\nnode_ref_lists=0\nkey_elements=1\nlen=5\n"
152
node_content = content[73:]
153
node_bytes = zlib.decompress(node_content)
154
expected_node = ("type=leaf\n"
155
"0000000000000000000000000000000000000000\x00\x00value:0\n"
156
"1111111111111111111111111111111111111111\x00\x00value:1\n"
157
"2222222222222222222222222222222222222222\x00\x00value:2\n"
158
"3333333333333333333333333333333333333333\x00\x00value:3\n"
159
"4444444444444444444444444444444444444444\x00\x00value:4\n")
160
self.assertEqual(expected_node, node_bytes)
162
def test_root_leaf_2_2(self):
163
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
164
nodes = self.make_nodes(5, 2, 2)
166
builder.add_node(*node)
167
# NamedTemporaryFile dies on builder.finish().read(). weird.
168
temp_file = builder.finish()
169
content = temp_file.read()
171
self.assertEqual(264, len(content))
173
"B+Tree Graph Index 1\nnode_ref_lists=2\nkey_elements=2\nlen=10\n"
176
node_content = content[74:]
177
node_bytes = zlib.decompress(node_content)
180
"0000000000000000000000000000000000000000\x000000000000000000000000000000000000000000\x00\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:0\n"
181
"0000000000000000000000000000000000000000\x001111111111111111111111111111111111111111\x000000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\r0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:1\n"
182
"0000000000000000000000000000000000000000\x002222222222222222222222222222222222222222\x00\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:2\n"
183
"0000000000000000000000000000000000000000\x003333333333333333333333333333333333333333\x000000000000000000000000000000000000000000\x00ref2222222222222222222222222222222222222222\t0000000000000000000000000000000000000000\x00ref2222222222222222222222222222222222222222\r0000000000000000000000000000000000000000\x00ref2222222222222222222222222222222222222222\x00value:3\n"
184
"0000000000000000000000000000000000000000\x004444444444444444444444444444444444444444\x00\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:4\n"
185
"1111111111111111111111111111111111111111\x000000000000000000000000000000000000000000\x00\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:0\n"
186
"1111111111111111111111111111111111111111\x001111111111111111111111111111111111111111\x001111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\r1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:1\n"
187
"1111111111111111111111111111111111111111\x002222222222222222222222222222222222222222\x00\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:2\n"
188
"1111111111111111111111111111111111111111\x003333333333333333333333333333333333333333\x001111111111111111111111111111111111111111\x00ref2222222222222222222222222222222222222222\t1111111111111111111111111111111111111111\x00ref2222222222222222222222222222222222222222\r1111111111111111111111111111111111111111\x00ref2222222222222222222222222222222222222222\x00value:3\n"
189
"1111111111111111111111111111111111111111\x004444444444444444444444444444444444444444\x00\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:4\n"
192
self.assertEqual(expected_node, node_bytes)
194
def test_2_leaves_1_0(self):
195
builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
196
nodes = self.make_nodes(800, 1, 0)
198
builder.add_node(*node)
199
# NamedTemporaryFile dies on builder.finish().read(). weird.
200
temp_file = builder.finish()
201
content = temp_file.read()
203
self.assertEqual(10646, len(content))
205
"B+Tree Graph Index 1\nnode_ref_lists=0\nkey_elements=1\nlen=800\n"
208
root = content[77:4096]
209
leaf1 = content[4096:8192]
210
leaf2 = content[8192:]
211
root_bytes = zlib.decompress(root)
215
"503503503503503503503503503503503503503503503503503503503503503503503503503503503503503503503503503503503503503503503503\n"
217
self.assertEqual(expected_root, root_bytes)
218
# We already know serialisation works for leaves, check key selection:
219
leaf1_bytes = zlib.decompress(leaf1)
220
sorted_node_keys = sorted(node[0] for node in nodes)
221
node = btree_index._LeafNode(leaf1_bytes, 1, 0)
222
self.assertEqual(448, len(node.keys))
223
self.assertEqual(sorted_node_keys[:448], sorted(node.keys))
224
leaf2_bytes = zlib.decompress(leaf2)
225
node = btree_index._LeafNode(leaf2_bytes, 1, 0)
226
self.assertEqual(800 - 448, len(node.keys))
227
self.assertEqual(sorted_node_keys[448:], sorted(node.keys))
229
def test_last_page_rounded_1_layer(self):
230
builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
231
nodes = self.make_nodes(10, 1, 0)
233
builder.add_node(*node)
234
# NamedTemporaryFile dies on builder.finish().read(). weird.
235
temp_file = builder.finish()
236
content = temp_file.read()
238
self.assertEqual(181, len(content))
240
"B+Tree Graph Index 1\nnode_ref_lists=0\nkey_elements=1\nlen=10\n"
243
# Check thelast page is well formed
245
leaf2_bytes = zlib.decompress(leaf2)
246
node = btree_index._LeafNode(leaf2_bytes, 1, 0)
247
self.assertEqual(10, len(node.keys))
248
sorted_node_keys = sorted(node[0] for node in nodes)
249
self.assertEqual(sorted_node_keys, sorted(node.keys))
251
def test_last_page_not_rounded_2_layer(self):
252
builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
253
nodes = self.make_nodes(800, 1, 0)
255
builder.add_node(*node)
256
# NamedTemporaryFile dies on builder.finish().read(). weird.
257
temp_file = builder.finish()
258
content = temp_file.read()
260
self.assertEqual(10646, len(content))
262
"B+Tree Graph Index 1\nnode_ref_lists=0\nkey_elements=1\nlen=800\n"
265
# Check thelast page is well formed
266
leaf2 = content[8192:]
267
leaf2_bytes = zlib.decompress(leaf2)
268
node = btree_index._LeafNode(leaf2_bytes, 1, 0)
269
self.assertEqual(800 - 448, len(node.keys))
270
sorted_node_keys = sorted(node[0] for node in nodes)
271
self.assertEqual(sorted_node_keys[448:], sorted(node.keys))
273
def test_three_level_tree_details(self):
274
# The left most pointer in the second internal node in a row should
275
# pointer to the second node that the internal node is for, _not_
276
# the first, otherwise the first node overlaps with the last node of
277
# the prior internal node on that row.
278
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
279
# 200K nodes is enough to create a two internal nodes on the second level
280
nodes = self.make_nodes(100000, 2, 2)
282
builder.add_node(*node)
283
transport = get_transport('trace+' + self.get_url(''))
284
size = transport.put_file('index', builder.finish())
286
index = btree_index.BTreeGraphIndex(transport, 'index', size)
287
# Seed the metadata, we're using internal calls now.
289
self.assertEqual(3, len(index._row_lengths),
290
"Not enough rows: %r" % index._row_lengths)
291
self.assertEqual(4, len(index._row_offsets))
292
self.assertEqual(sum(index._row_lengths), index._row_offsets[-1])
293
internal_nodes = index._get_internal_nodes([0, 1, 2])
294
root_node = internal_nodes[0]
295
internal_node1 = internal_nodes[1]
296
internal_node2 = internal_nodes[2]
297
# The left most node node2 points at should be one after the right most
298
# node pointed at by node1.
299
self.assertEqual(internal_node2.offset, 1 + len(internal_node1.keys))
300
# The left most key of the second node pointed at by internal_node2
301
# should be its first key. We can check this by looking for its first key
302
# in the second node it points at
303
pos = index._row_offsets[2] + internal_node2.offset + 1
304
leaf = index._get_leaf_nodes([pos])[pos]
305
self.assertTrue(internal_node2.keys[0] in leaf.keys)
307
def test_2_leaves_2_2(self):
308
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
309
nodes = self.make_nodes(200, 2, 2)
311
builder.add_node(*node)
312
# NamedTemporaryFile dies on builder.finish().read(). weird.
313
temp_file = builder.finish()
314
content = temp_file.read()
316
self.assertEqual(10574, len(content))
318
"B+Tree Graph Index 1\nnode_ref_lists=2\nkey_elements=2\nlen=400\n"
321
root = content[77:4096]
322
leaf1 = content[4096:8192]
323
leaf2 = content[8192:]
324
root_bytes = zlib.decompress(root)
328
"1111111111111111111111111111111111111111\x00"
329
"126126126126126126126126126126126126126126126126126126126"
330
"126126126126126126126126126126126126126126126126126126126126126\n"
332
self.assertEqual(expected_root, root_bytes)
333
# We assume the other leaf nodes have been written correctly - layering
336
def test_spill_index_stress_1_1(self):
337
builder = btree_index.BTreeBuilder(key_elements=1, spill_at=2)
338
nodes = [node[0:2] for node in self.make_nodes(16, 1, 0)]
339
builder.add_node(*nodes[0])
340
# Test the parts of the index that take up memory are doing so
342
self.assertEqual(1, len(builder._nodes))
343
self.assertEqual(1, len(builder._keys))
344
self.assertEqual({}, builder._nodes_by_key)
345
builder.add_node(*nodes[1])
346
self.assertEqual(0, len(builder._nodes))
347
self.assertEqual(0, len(builder._keys))
348
self.assertEqual({}, builder._nodes_by_key)
349
self.assertEqual(1, len(builder._backing_indices))
350
self.assertEqual(2, builder._backing_indices[0].key_count())
352
builder.add_node(*nodes[2])
353
self.assertEqual(1, len(builder._nodes))
354
self.assertEqual(1, len(builder._keys))
355
self.assertEqual({}, builder._nodes_by_key)
356
# And spills to a second backing index combing all
357
builder.add_node(*nodes[3])
358
self.assertEqual(0, len(builder._nodes))
359
self.assertEqual(0, len(builder._keys))
360
self.assertEqual({}, builder._nodes_by_key)
361
self.assertEqual(2, len(builder._backing_indices))
362
self.assertEqual(None, builder._backing_indices[0])
363
self.assertEqual(4, builder._backing_indices[1].key_count())
364
# The next spills to the 2-len slot
365
builder.add_node(*nodes[4])
366
builder.add_node(*nodes[5])
367
self.assertEqual(0, len(builder._nodes))
368
self.assertEqual(0, len(builder._keys))
369
self.assertEqual({}, builder._nodes_by_key)
370
self.assertEqual(2, len(builder._backing_indices))
371
self.assertEqual(2, builder._backing_indices[0].key_count())
372
self.assertEqual(4, builder._backing_indices[1].key_count())
373
# Next spill combines
374
builder.add_node(*nodes[6])
375
builder.add_node(*nodes[7])
376
self.assertEqual(3, len(builder._backing_indices))
377
self.assertEqual(None, builder._backing_indices[0])
378
self.assertEqual(None, builder._backing_indices[1])
379
self.assertEqual(8, builder._backing_indices[2].key_count())
380
# And so forth - counting up in binary.
381
builder.add_node(*nodes[8])
382
builder.add_node(*nodes[9])
383
self.assertEqual(3, len(builder._backing_indices))
384
self.assertEqual(2, builder._backing_indices[0].key_count())
385
self.assertEqual(None, builder._backing_indices[1])
386
self.assertEqual(8, builder._backing_indices[2].key_count())
387
builder.add_node(*nodes[10])
388
builder.add_node(*nodes[11])
389
self.assertEqual(3, len(builder._backing_indices))
390
self.assertEqual(None, builder._backing_indices[0])
391
self.assertEqual(4, builder._backing_indices[1].key_count())
392
self.assertEqual(8, builder._backing_indices[2].key_count())
393
builder.add_node(*nodes[12])
394
# Test that memory and disk are both used for query methods; and that
395
# None is skipped over happily.
396
self.assertEqual([(builder,) + node for node in sorted(nodes[:13])],
397
list(builder.iter_all_entries()))
398
# Two nodes - one memory one disk
399
self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
400
set(builder.iter_entries([nodes[12][0], nodes[11][0]])))
401
self.assertEqual(13, builder.key_count())
402
self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
403
set(builder.iter_entries_prefix([nodes[12][0], nodes[11][0]])))
404
builder.add_node(*nodes[13])
405
self.assertEqual(3, len(builder._backing_indices))
406
self.assertEqual(2, builder._backing_indices[0].key_count())
407
self.assertEqual(4, builder._backing_indices[1].key_count())
408
self.assertEqual(8, builder._backing_indices[2].key_count())
409
builder.add_node(*nodes[14])
410
builder.add_node(*nodes[15])
411
self.assertEqual(4, len(builder._backing_indices))
412
self.assertEqual(None, builder._backing_indices[0])
413
self.assertEqual(None, builder._backing_indices[1])
414
self.assertEqual(None, builder._backing_indices[2])
415
self.assertEqual(16, builder._backing_indices[3].key_count())
416
# Now finish, and check we got a correctly ordered tree
417
transport = self.get_transport('')
418
size = transport.put_file('index', builder.finish())
419
index = btree_index.BTreeGraphIndex(transport, 'index', size)
420
nodes = list(index.iter_all_entries())
421
self.assertEqual(sorted(nodes), nodes)
422
self.assertEqual(16, len(nodes))
424
def test_spill_index_stress_2_2(self):
425
# test that references and longer keys don't confuse things.
426
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2,
428
nodes = self.make_nodes(16, 2, 2)
429
builder.add_node(*nodes[0])
430
# Test the parts of the index that take up memory are doing so
432
self.assertEqual(1, len(builder._keys))
433
self.assertEqual(2, len(builder._nodes))
434
self.assertNotEqual({}, builder._nodes_by_key)
435
builder.add_node(*nodes[1])
436
self.assertEqual(0, len(builder._keys))
437
self.assertEqual(0, len(builder._nodes))
438
self.assertEqual({}, builder._nodes_by_key)
439
self.assertEqual(1, len(builder._backing_indices))
440
self.assertEqual(2, builder._backing_indices[0].key_count())
442
builder.add_node(*nodes[2])
443
self.assertEqual(2, len(builder._nodes))
444
self.assertEqual(1, len(builder._keys))
445
self.assertNotEqual({}, builder._nodes_by_key)
446
# And spills to a second backing index combing all
447
builder.add_node(*nodes[3])
448
self.assertEqual(0, len(builder._nodes))
449
self.assertEqual(0, len(builder._keys))
450
self.assertEqual({}, builder._nodes_by_key)
451
self.assertEqual(2, len(builder._backing_indices))
452
self.assertEqual(None, builder._backing_indices[0])
453
self.assertEqual(4, builder._backing_indices[1].key_count())
454
# The next spills to the 2-len slot
455
builder.add_node(*nodes[4])
456
builder.add_node(*nodes[5])
457
self.assertEqual(0, len(builder._nodes))
458
self.assertEqual(0, len(builder._keys))
459
self.assertEqual({}, builder._nodes_by_key)
460
self.assertEqual(2, len(builder._backing_indices))
461
self.assertEqual(2, builder._backing_indices[0].key_count())
462
self.assertEqual(4, builder._backing_indices[1].key_count())
463
# Next spill combines
464
builder.add_node(*nodes[6])
465
builder.add_node(*nodes[7])
466
self.assertEqual(3, len(builder._backing_indices))
467
self.assertEqual(None, builder._backing_indices[0])
468
self.assertEqual(None, builder._backing_indices[1])
469
self.assertEqual(8, builder._backing_indices[2].key_count())
470
# And so forth - counting up in binary.
471
builder.add_node(*nodes[8])
472
builder.add_node(*nodes[9])
473
self.assertEqual(3, len(builder._backing_indices))
474
self.assertEqual(2, builder._backing_indices[0].key_count())
475
self.assertEqual(None, builder._backing_indices[1])
476
self.assertEqual(8, builder._backing_indices[2].key_count())
477
builder.add_node(*nodes[10])
478
builder.add_node(*nodes[11])
479
self.assertEqual(3, len(builder._backing_indices))
480
self.assertEqual(None, builder._backing_indices[0])
481
self.assertEqual(4, builder._backing_indices[1].key_count())
482
self.assertEqual(8, builder._backing_indices[2].key_count())
483
builder.add_node(*nodes[12])
484
# Test that memory and disk are both used for query methods; and that
485
# None is skipped over happily.
486
self.assertEqual([(builder,) + node for node in sorted(nodes[:13])],
487
list(builder.iter_all_entries()))
488
# Two nodes - one memory one disk
489
self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
490
set(builder.iter_entries([nodes[12][0], nodes[11][0]])))
491
self.assertEqual(13, builder.key_count())
492
self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
493
set(builder.iter_entries_prefix([nodes[12][0], nodes[11][0]])))
494
builder.add_node(*nodes[13])
495
self.assertEqual(3, len(builder._backing_indices))
496
self.assertEqual(2, builder._backing_indices[0].key_count())
497
self.assertEqual(4, builder._backing_indices[1].key_count())
498
self.assertEqual(8, builder._backing_indices[2].key_count())
499
builder.add_node(*nodes[14])
500
builder.add_node(*nodes[15])
501
self.assertEqual(4, len(builder._backing_indices))
502
self.assertEqual(None, builder._backing_indices[0])
503
self.assertEqual(None, builder._backing_indices[1])
504
self.assertEqual(None, builder._backing_indices[2])
505
self.assertEqual(16, builder._backing_indices[3].key_count())
506
# Now finish, and check we got a correctly ordered tree
507
transport = self.get_transport('')
508
size = transport.put_file('index', builder.finish())
509
index = btree_index.BTreeGraphIndex(transport, 'index', size)
510
nodes = list(index.iter_all_entries())
511
self.assertEqual(sorted(nodes), nodes)
512
self.assertEqual(16, len(nodes))
514
def test_spill_index_duplicate_key_caught_on_finish(self):
515
builder = btree_index.BTreeBuilder(key_elements=1, spill_at=2)
516
nodes = [node[0:2] for node in self.make_nodes(16, 1, 0)]
517
builder.add_node(*nodes[0])
518
builder.add_node(*nodes[1])
519
builder.add_node(*nodes[0])
520
self.assertRaises(errors.BadIndexDuplicateKey, builder.finish)
523
class TestBTreeIndex(BTreeTestCase):
525
def make_index(self, ref_lists=0, key_elements=1, nodes=[]):
526
builder = btree_index.BTreeBuilder(reference_lists=ref_lists,
527
key_elements=key_elements)
528
for key, value, references in nodes:
529
builder.add_node(key, value, references)
530
stream = builder.finish()
531
trans = get_transport('trace+' + self.get_url())
532
size = trans.put_file('index', stream)
533
return btree_index.BTreeGraphIndex(trans, 'index', size)
535
def test_trivial_constructor(self):
536
transport = get_transport('trace+' + self.get_url(''))
537
index = btree_index.BTreeGraphIndex(transport, 'index', None)
538
# Checks the page size at load, but that isn't logged yet.
539
self.assertEqual([], transport._activity)
541
def test_with_size_constructor(self):
542
transport = get_transport('trace+' + self.get_url(''))
543
index = btree_index.BTreeGraphIndex(transport, 'index', 1)
544
# Checks the page size at load, but that isn't logged yet.
545
self.assertEqual([], transport._activity)
547
def test_empty_key_count_no_size(self):
548
builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
549
transport = get_transport('trace+' + self.get_url(''))
550
transport.put_file('index', builder.finish())
551
index = btree_index.BTreeGraphIndex(transport, 'index', None)
552
del transport._activity[:]
553
self.assertEqual([], transport._activity)
554
self.assertEqual(0, index.key_count())
555
# The entire index should have been requested (as we generally have the
556
# size available, and doing many small readvs is inappropriate).
557
# We can't tell how much was actually read here, but - check the code.
558
self.assertEqual([('get', 'index'),
559
('readv', 'index', [(0, 72)], False, None)],
562
def test_empty_key_count(self):
563
builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
564
transport = get_transport('trace+' + self.get_url(''))
565
size = transport.put_file('index', builder.finish())
566
self.assertEqual(72, size)
567
index = btree_index.BTreeGraphIndex(transport, 'index', size)
568
del transport._activity[:]
569
self.assertEqual([], transport._activity)
570
self.assertEqual(0, index.key_count())
571
# The entire index should have been read, as 4K > size
572
self.assertEqual([('readv', 'index', [(0, 72)], False, None)],
575
def test_non_empty_key_count_2_2(self):
576
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
577
nodes = self.make_nodes(35, 2, 2)
579
builder.add_node(*node)
580
transport = get_transport('trace+' + self.get_url(''))
581
size = transport.put_file('index', builder.finish())
582
index = btree_index.BTreeGraphIndex(transport, 'index', size)
583
del transport._activity[:]
584
self.assertEqual([], transport._activity)
585
self.assertEqual(70, index.key_count())
586
# The entire index should have been read, as it is one page long.
587
self.assertEqual([('readv', 'index', [(0, size)], False, None)],
589
self.assertEqual(1593, size)
591
def test_2_levels_key_count_2_2(self):
592
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
593
nodes = self.make_nodes(160, 2, 2)
595
builder.add_node(*node)
596
transport = get_transport('trace+' + self.get_url(''))
597
size = transport.put_file('index', builder.finish())
598
self.assertEqual(10242, size)
599
index = btree_index.BTreeGraphIndex(transport, 'index', size)
600
del transport._activity[:]
601
self.assertEqual([], transport._activity)
602
self.assertEqual(320, index.key_count())
603
# The entire index should not have been read.
604
self.assertEqual([('readv', 'index', [(0, 4096)], False, None)],
607
def test_validate_one_page(self):
608
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
609
nodes = self.make_nodes(80, 2, 2)
611
builder.add_node(*node)
612
transport = get_transport('trace+' + self.get_url(''))
613
size = transport.put_file('index', builder.finish())
614
index = btree_index.BTreeGraphIndex(transport, 'index', size)
615
del transport._activity[:]
616
self.assertEqual([], transport._activity)
618
# The entire index should have been read linearly.
619
self.assertEqual([('readv', 'index', [(0, size)], False, None)],
621
self.assertEqual(3846, size)
623
def test_validate_two_pages(self):
624
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
625
nodes = self.make_nodes(160, 2, 2)
627
builder.add_node(*node)
628
transport = get_transport('trace+' + self.get_url(''))
629
size = transport.put_file('index', builder.finish())
630
# Root page, 2 leaf pages
631
self.assertEqual(10242, size)
632
index = btree_index.BTreeGraphIndex(transport, 'index', size)
633
del transport._activity[:]
634
self.assertEqual([], transport._activity)
636
# The entire index should have been read linearly.
637
self.assertEqual([('readv', 'index', [(0, 4096)], False, None),
638
('readv', 'index', [(4096, 4096), (8192, 2050)], False, None)],
640
# XXX: TODO: write some badly-ordered nodes, and some pointers-to-wrong
641
# node and make validate find them.
643
def test_eq_ne(self):
644
# two indices are equal when constructed with the same parameters:
645
transport1 = get_transport('trace+' + self.get_url(''))
646
transport2 = get_transport(self.get_url(''))
648
btree_index.BTreeGraphIndex(transport1, 'index', None) ==
649
btree_index.BTreeGraphIndex(transport1, 'index', None))
651
btree_index.BTreeGraphIndex(transport1, 'index', 20) ==
652
btree_index.BTreeGraphIndex(transport1, 'index', 20))
654
btree_index.BTreeGraphIndex(transport1, 'index', 20) ==
655
btree_index.BTreeGraphIndex(transport2, 'index', 20))
657
btree_index.BTreeGraphIndex(transport1, 'inde1', 20) ==
658
btree_index.BTreeGraphIndex(transport1, 'inde2', 20))
660
btree_index.BTreeGraphIndex(transport1, 'index', 10) ==
661
btree_index.BTreeGraphIndex(transport1, 'index', 20))
663
btree_index.BTreeGraphIndex(transport1, 'index', None) !=
664
btree_index.BTreeGraphIndex(transport1, 'index', None))
666
btree_index.BTreeGraphIndex(transport1, 'index', 20) !=
667
btree_index.BTreeGraphIndex(transport1, 'index', 20))
669
btree_index.BTreeGraphIndex(transport1, 'index', 20) !=
670
btree_index.BTreeGraphIndex(transport2, 'index', 20))
672
btree_index.BTreeGraphIndex(transport1, 'inde1', 20) !=
673
btree_index.BTreeGraphIndex(transport1, 'inde2', 20))
675
btree_index.BTreeGraphIndex(transport1, 'index', 10) !=
676
btree_index.BTreeGraphIndex(transport1, 'index', 20))
678
def test_iter_all_entries_reads(self):
679
# iterating all entries reads the header, then does a linear
681
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
682
# 200k nodes is enough to create a three-level index.
683
nodes = self.make_nodes(100000, 2, 2)
685
builder.add_node(*node)
686
transport = get_transport('trace+' + self.get_url(''))
687
size = transport.put_file('index', builder.finish())
688
self.assertEqual(4058469, size, 'number of expected bytes in the'
691
index = btree_index.BTreeGraphIndex(transport, 'index', size)
692
del transport._activity[:]
693
self.assertEqual([], transport._activity)
694
found_nodes = list(index.iter_all_entries())
696
for node in found_nodes:
697
self.assertTrue(node[0] is index)
698
bare_nodes.append(node[1:])
699
self.assertEqual(3, len(index._row_lengths),
700
"Not enough rows: %r" % index._row_lengths)
701
# Should be as long as the nodes we supplied
702
self.assertEqual(200000, len(found_nodes))
703
# Should have the same content
704
self.assertEqual(set(nodes), set(bare_nodes))
705
# Should have done linear scan IO up the index, ignoring
706
# the internal nodes:
707
# The entire index should have been read
708
total_pages = sum(index._row_lengths)
709
self.assertEqual(total_pages, index._row_offsets[-1])
710
self.assertEqual(4058469, size)
711
# The start of the leaves
712
first_byte = index._row_offsets[-2] * btree_index._PAGE_SIZE
714
for offset in range(first_byte, size, 4096):
715
readv_request.append((offset, 4096))
716
readv_request[-1] = (readv_request[-1][0], 3429)
717
expected = [('readv', 'index', [(0, 4096)], False, None),
718
('readv', 'index', readv_request, False, None)]
719
if expected != transport._activity:
720
self.assertEqualDiff(pprint.pformat(expected),
721
pprint.pformat(transport._activity))
723
def _test_iter_entries_references_resolved(self):
724
index = self.make_index(1, nodes=[
725
(('name', ), 'data', ([('ref', ), ('ref', )], )),
726
(('ref', ), 'refdata', ([], ))])
727
self.assertEqual(set([(index, ('name', ), 'data', ((('ref',),('ref',)),)),
728
(index, ('ref', ), 'refdata', ((), ))]),
729
set(index.iter_entries([('name',), ('ref',)])))
731
def test_iter_entries_references_2_refs_resolved(self):
732
# iterating some entries reads just the pages needed. For now, to
733
# get it working and start measuring, only 4K pages are read.
734
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
735
# 80 nodes is enough to create a two-level index.
736
nodes = self.make_nodes(160, 2, 2)
738
builder.add_node(*node)
739
transport = get_transport('trace+' + self.get_url(''))
740
size = transport.put_file('index', builder.finish())
742
index = btree_index.BTreeGraphIndex(transport, 'index', size)
743
del transport._activity[:]
744
self.assertEqual([], transport._activity)
746
found_nodes = list(index.iter_entries([nodes[30][0]]))
748
for node in found_nodes:
749
self.assertTrue(node[0] is index)
750
bare_nodes.append(node[1:])
751
# Should be as long as the nodes we supplied
752
self.assertEqual(1, len(found_nodes))
753
# Should have the same content
754
self.assertEqual(nodes[30], bare_nodes[0])
755
# Should have read the root node, then one leaf page:
756
self.assertEqual([('readv', 'index', [(0, 4096)], False, None),
757
('readv', 'index', [(4096, 4096), ], False, None)],
760
def test_iter_key_prefix_1_element_key_None(self):
761
index = self.make_index()
762
self.assertRaises(errors.BadIndexKey, list,
763
index.iter_entries_prefix([(None, )]))
765
def test_iter_key_prefix_wrong_length(self):
766
index = self.make_index()
767
self.assertRaises(errors.BadIndexKey, list,
768
index.iter_entries_prefix([('foo', None)]))
769
index = self.make_index(key_elements=2)
770
self.assertRaises(errors.BadIndexKey, list,
771
index.iter_entries_prefix([('foo', )]))
772
self.assertRaises(errors.BadIndexKey, list,
773
index.iter_entries_prefix([('foo', None, None)]))
775
def test_iter_key_prefix_1_key_element_no_refs(self):
776
index = self.make_index( nodes=[
777
(('name', ), 'data', ()),
778
(('ref', ), 'refdata', ())])
779
self.assertEqual(set([(index, ('name', ), 'data'),
780
(index, ('ref', ), 'refdata')]),
781
set(index.iter_entries_prefix([('name', ), ('ref', )])))
783
def test_iter_key_prefix_1_key_element_refs(self):
784
index = self.make_index(1, nodes=[
785
(('name', ), 'data', ([('ref', )], )),
786
(('ref', ), 'refdata', ([], ))])
787
self.assertEqual(set([(index, ('name', ), 'data', ((('ref',),),)),
788
(index, ('ref', ), 'refdata', ((), ))]),
789
set(index.iter_entries_prefix([('name', ), ('ref', )])))
791
def test_iter_key_prefix_2_key_element_no_refs(self):
792
index = self.make_index(key_elements=2, nodes=[
793
(('name', 'fin1'), 'data', ()),
794
(('name', 'fin2'), 'beta', ()),
795
(('ref', 'erence'), 'refdata', ())])
796
self.assertEqual(set([(index, ('name', 'fin1'), 'data'),
797
(index, ('ref', 'erence'), 'refdata')]),
798
set(index.iter_entries_prefix([('name', 'fin1'), ('ref', 'erence')])))
799
self.assertEqual(set([(index, ('name', 'fin1'), 'data'),
800
(index, ('name', 'fin2'), 'beta')]),
801
set(index.iter_entries_prefix([('name', None)])))
803
def test_iter_key_prefix_2_key_element_refs(self):
804
index = self.make_index(1, key_elements=2, nodes=[
805
(('name', 'fin1'), 'data', ([('ref', 'erence')], )),
806
(('name', 'fin2'), 'beta', ([], )),
807
(('ref', 'erence'), 'refdata', ([], ))])
808
self.assertEqual(set([(index, ('name', 'fin1'), 'data', ((('ref', 'erence'),),)),
809
(index, ('ref', 'erence'), 'refdata', ((), ))]),
810
set(index.iter_entries_prefix([('name', 'fin1'), ('ref', 'erence')])))
811
self.assertEqual(set([(index, ('name', 'fin1'), 'data', ((('ref', 'erence'),),)),
812
(index, ('name', 'fin2'), 'beta', ((), ))]),
813
set(index.iter_entries_prefix([('name', None)])))
816
class TestBTreeNodes(BTreeTestCase):
818
def restore_parser(self):
819
btree_index._parse_btree = self.saved_parser
822
BTreeTestCase.setUp(self)
823
self.saved_parser = btree_index._parse_btree
824
self.addCleanup(self.restore_parser)
825
btree_index._parse_btree = self.parse_btree
827
def test_LeafNode_1_0(self):
828
node_bytes = ("type=leaf\n"
829
"0000000000000000000000000000000000000000\x00\x00value:0\n"
830
"1111111111111111111111111111111111111111\x00\x00value:1\n"
831
"2222222222222222222222222222222222222222\x00\x00value:2\n"
832
"3333333333333333333333333333333333333333\x00\x00value:3\n"
833
"4444444444444444444444444444444444444444\x00\x00value:4\n")
834
node = btree_index._LeafNode(node_bytes, 1, 0)
835
# We do direct access, or don't care about order, to leaf nodes most of
836
# the time, so a dict is useful:
838
("0000000000000000000000000000000000000000",): ("value:0", ()),
839
("1111111111111111111111111111111111111111",): ("value:1", ()),
840
("2222222222222222222222222222222222222222",): ("value:2", ()),
841
("3333333333333333333333333333333333333333",): ("value:3", ()),
842
("4444444444444444444444444444444444444444",): ("value:4", ()),
845
def test_LeafNode_2_2(self):
846
node_bytes = ("type=leaf\n"
847
"00\x0000\x00\t00\x00ref00\x00value:0\n"
848
"00\x0011\x0000\x00ref00\t00\x00ref00\r01\x00ref01\x00value:1\n"
849
"11\x0033\x0011\x00ref22\t11\x00ref22\r11\x00ref22\x00value:3\n"
850
"11\x0044\x00\t11\x00ref00\x00value:4\n"
853
node = btree_index._LeafNode(node_bytes, 2, 2)
854
# We do direct access, or don't care about order, to leaf nodes most of
855
# the time, so a dict is useful:
857
('00', '00'): ('value:0', ((), (('00', 'ref00'),))),
858
('00', '11'): ('value:1',
859
((('00', 'ref00'),), (('00', 'ref00'), ('01', 'ref01')))),
860
('11', '33'): ('value:3',
861
((('11', 'ref22'),), (('11', 'ref22'), ('11', 'ref22')))),
862
('11', '44'): ('value:4', ((), (('11', 'ref00'),)))
865
def test_InternalNode_1(self):
866
node_bytes = ("type=internal\n"
868
"0000000000000000000000000000000000000000\n"
869
"1111111111111111111111111111111111111111\n"
870
"2222222222222222222222222222222222222222\n"
871
"3333333333333333333333333333333333333333\n"
872
"4444444444444444444444444444444444444444\n"
874
node = btree_index._InternalNode(node_bytes)
875
# We want to bisect to find the right children from this node, so a
876
# vector is most useful.
878
("0000000000000000000000000000000000000000",),
879
("1111111111111111111111111111111111111111",),
880
("2222222222222222222222222222222222222222",),
881
("3333333333333333333333333333333333333333",),
882
("4444444444444444444444444444444444444444",),
884
self.assertEqual(1, node.offset)
886
def test_LeafNode_2_2(self):
887
node_bytes = ("type=leaf\n"
888
"00\x0000\x00\t00\x00ref00\x00value:0\n"
889
"00\x0011\x0000\x00ref00\t00\x00ref00\r01\x00ref01\x00value:1\n"
890
"11\x0033\x0011\x00ref22\t11\x00ref22\r11\x00ref22\x00value:3\n"
891
"11\x0044\x00\t11\x00ref00\x00value:4\n"
894
node = btree_index._LeafNode(node_bytes, 2, 2)
895
# We do direct access, or don't care about order, to leaf nodes most of
896
# the time, so a dict is useful:
898
('00', '00'): ('value:0', ((), (('00', 'ref00'),))),
899
('00', '11'): ('value:1',
900
((('00', 'ref00'),), (('00', 'ref00'), ('01', 'ref01')))),
901
('11', '33'): ('value:3',
902
((('11', 'ref22'),), (('11', 'ref22'), ('11', 'ref22')))),
903
('11', '44'): ('value:4', ((), (('11', 'ref00'),)))
907
class TestCompiledBtree(tests.TestCase):
909
def test_exists(self):
910
# This is just to let the user know if they don't have the feature
912
self.requireFeature(CompiledBtreeParserFeature)
915
class TestMultiBisectRight(tests.TestCase):
917
def assertMultiBisectRight(self, offsets, search_keys, fixed_keys):
918
self.assertEqual(offsets,
919
btree_index.BTreeGraphIndex._multi_bisect_right(
920
search_keys, fixed_keys))
922
def test_after(self):
923
self.assertMultiBisectRight([(1, ['b'])], ['b'], ['a'])
924
self.assertMultiBisectRight([(3, ['e', 'f', 'g'])],
925
['e', 'f', 'g'], ['a', 'b', 'c'])
927
def test_before(self):
928
self.assertMultiBisectRight([(0, ['a'])], ['a'], ['b'])
929
self.assertMultiBisectRight([(0, ['a', 'b', 'c', 'd'])],
930
['a', 'b', 'c', 'd'], ['e', 'f', 'g'])
932
def test_exact(self):
933
self.assertMultiBisectRight([(1, ['a'])], ['a'], ['a'])
934
self.assertMultiBisectRight([(1, ['a']), (2, ['b'])], ['a', 'b'], ['a', 'b'])
935
self.assertMultiBisectRight([(1, ['a']), (3, ['c'])],
936
['a', 'c'], ['a', 'b', 'c'])
938
def test_inbetween(self):
939
self.assertMultiBisectRight([(1, ['b'])], ['b'], ['a', 'c'])
940
self.assertMultiBisectRight([(1, ['b', 'c', 'd']), (2, ['f', 'g'])],
941
['b', 'c', 'd', 'f', 'g'], ['a', 'e', 'h'])
943
def test_mixed(self):
944
self.assertMultiBisectRight([(0, ['a', 'b']), (2, ['d', 'e']),
946
['a', 'b', 'd', 'e', 'g', 'h'],
947
['c', 'd', 'f', 'g'])