633
618
for key, value, references in nodes:
634
619
builder.add_node(key, value, references)
635
620
stream = builder.finish()
636
trans = get_transport('trace+' + self.get_url())
621
trans = transport.get_transport_from_url('trace+' + self.get_url())
637
622
size = trans.put_file('index', stream)
638
623
return btree_index.BTreeGraphIndex(trans, 'index', size)
625
def make_index_with_offset(self, ref_lists=1, key_elements=1, nodes=[],
627
builder = btree_index.BTreeBuilder(key_elements=key_elements,
628
reference_lists=ref_lists)
629
builder.add_nodes(nodes)
630
transport = self.get_transport('')
631
# NamedTemporaryFile dies on builder.finish().read(). weird.
632
temp_file = builder.finish()
633
content = temp_file.read()
636
transport.put_bytes('index', (' '*offset)+content)
637
return btree_index.BTreeGraphIndex(transport, 'index', size=size,
640
def test_clear_cache(self):
641
nodes = self.make_nodes(160, 2, 2)
642
index = self.make_index(ref_lists=2, key_elements=2, nodes=nodes)
643
self.assertEqual(1, len(list(index.iter_entries([nodes[30][0]]))))
644
self.assertEqual([1, 4], index._row_lengths)
645
self.assertIsNot(None, index._root_node)
646
internal_node_pre_clear = index._internal_node_cache.keys()
647
self.assertTrue(len(index._leaf_node_cache) > 0)
649
# We don't touch _root_node or _internal_node_cache, both should be
650
# small, and can save a round trip or two
651
self.assertIsNot(None, index._root_node)
652
# NOTE: We don't want to affect the _internal_node_cache, as we expect
653
# it will be small, and if we ever do touch this index again, it
654
# will save round-trips. This assertion isn't very strong,
655
# becuase without a 3-level index, we don't have any internal
657
self.assertEqual(internal_node_pre_clear,
658
index._internal_node_cache.keys())
659
self.assertEqual(0, len(index._leaf_node_cache))
640
661
def test_trivial_constructor(self):
641
transport = get_transport('trace+' + self.get_url(''))
642
index = btree_index.BTreeGraphIndex(transport, 'index', None)
662
t = transport.get_transport_from_url('trace+' + self.get_url(''))
663
index = btree_index.BTreeGraphIndex(t, 'index', None)
643
664
# Checks the page size at load, but that isn't logged yet.
644
self.assertEqual([], transport._activity)
665
self.assertEqual([], t._activity)
646
667
def test_with_size_constructor(self):
647
transport = get_transport('trace+' + self.get_url(''))
648
index = btree_index.BTreeGraphIndex(transport, 'index', 1)
668
t = transport.get_transport_from_url('trace+' + self.get_url(''))
669
index = btree_index.BTreeGraphIndex(t, 'index', 1)
649
670
# Checks the page size at load, but that isn't logged yet.
650
self.assertEqual([], transport._activity)
671
self.assertEqual([], t._activity)
652
673
def test_empty_key_count_no_size(self):
653
674
builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
654
transport = get_transport('trace+' + self.get_url(''))
655
transport.put_file('index', builder.finish())
656
index = btree_index.BTreeGraphIndex(transport, 'index', None)
657
del transport._activity[:]
658
self.assertEqual([], transport._activity)
675
t = transport.get_transport_from_url('trace+' + self.get_url(''))
676
t.put_file('index', builder.finish())
677
index = btree_index.BTreeGraphIndex(t, 'index', None)
679
self.assertEqual([], t._activity)
659
680
self.assertEqual(0, index.key_count())
660
681
# The entire index should have been requested (as we generally have the
661
682
# size available, and doing many small readvs is inappropriate).
662
683
# We can't tell how much was actually read here, but - check the code.
663
self.assertEqual([('get', 'index')], transport._activity)
684
self.assertEqual([('get', 'index')], t._activity)
665
686
def test_empty_key_count(self):
666
687
builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
667
transport = get_transport('trace+' + self.get_url(''))
668
size = transport.put_file('index', builder.finish())
688
t = transport.get_transport_from_url('trace+' + self.get_url(''))
689
size = t.put_file('index', builder.finish())
669
690
self.assertEqual(72, size)
670
index = btree_index.BTreeGraphIndex(transport, 'index', size)
671
del transport._activity[:]
672
self.assertEqual([], transport._activity)
691
index = btree_index.BTreeGraphIndex(t, 'index', size)
693
self.assertEqual([], t._activity)
673
694
self.assertEqual(0, index.key_count())
674
695
# The entire index should have been read, as 4K > size
675
696
self.assertEqual([('readv', 'index', [(0, 72)], False, None)],
678
699
def test_non_empty_key_count_2_2(self):
679
700
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
680
701
nodes = self.make_nodes(35, 2, 2)
681
702
for node in nodes:
682
703
builder.add_node(*node)
683
transport = get_transport('trace+' + self.get_url(''))
684
size = transport.put_file('index', builder.finish())
685
index = btree_index.BTreeGraphIndex(transport, 'index', size)
686
del transport._activity[:]
687
self.assertEqual([], transport._activity)
704
t = transport.get_transport_from_url('trace+' + self.get_url(''))
705
size = t.put_file('index', builder.finish())
706
index = btree_index.BTreeGraphIndex(t, 'index', size)
708
self.assertEqual([], t._activity)
688
709
self.assertEqual(70, index.key_count())
689
710
# The entire index should have been read, as it is one page long.
690
711
self.assertEqual([('readv', 'index', [(0, size)], False, None)],
692
self.assertEqual(1199, size)
713
self.assertEqualsApproxCompressed(1173, size)
715
def test_with_offset_no_size(self):
716
index = self.make_index_with_offset(key_elements=1, ref_lists=1,
718
nodes=self.make_nodes(200, 1, 1))
719
index._size = None # throw away the size info
720
self.assertEqual(200, index.key_count())
722
def test_with_small_offset(self):
723
index = self.make_index_with_offset(key_elements=1, ref_lists=1,
725
nodes=self.make_nodes(200, 1, 1))
726
self.assertEqual(200, index.key_count())
728
def test_with_large_offset(self):
729
index = self.make_index_with_offset(key_elements=1, ref_lists=1,
731
nodes=self.make_nodes(200, 1, 1))
732
self.assertEqual(200, index.key_count())
694
734
def test__read_nodes_no_size_one_page_reads_once(self):
695
735
self.make_index(nodes=[(('key',), 'value', ())])
696
trans = get_transport('trace+' + self.get_url())
736
trans = transport.get_transport_from_url('trace+' + self.get_url())
697
737
index = btree_index.BTreeGraphIndex(trans, 'index', None)
698
738
del trans._activity[:]
699
739
nodes = dict(index._read_nodes([0]))
700
740
self.assertEqual([0], nodes.keys())
702
self.assertEqual([('key',)], node.keys.keys())
742
self.assertEqual([('key',)], node.all_keys())
703
743
self.assertEqual([('get', 'index')], trans._activity)
705
745
def test__read_nodes_no_size_multiple_pages(self):
718
758
nodes = self.make_nodes(160, 2, 2)
719
759
for node in nodes:
720
760
builder.add_node(*node)
721
transport = get_transport('trace+' + self.get_url(''))
722
size = transport.put_file('index', builder.finish())
723
self.assertEqual(17692, size)
724
index = btree_index.BTreeGraphIndex(transport, 'index', size)
725
del transport._activity[:]
726
self.assertEqual([], transport._activity)
761
t = transport.get_transport_from_url('trace+' + self.get_url(''))
762
size = t.put_file('index', builder.finish())
763
self.assertEqualsApproxCompressed(17692, size)
764
index = btree_index.BTreeGraphIndex(t, 'index', size)
766
self.assertEqual([], t._activity)
727
767
self.assertEqual(320, index.key_count())
728
768
# The entire index should not have been read.
729
769
self.assertEqual([('readv', 'index', [(0, 4096)], False, None)],
732
772
def test_validate_one_page(self):
733
773
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
734
774
nodes = self.make_nodes(45, 2, 2)
735
775
for node in nodes:
736
776
builder.add_node(*node)
737
transport = get_transport('trace+' + self.get_url(''))
738
size = transport.put_file('index', builder.finish())
739
index = btree_index.BTreeGraphIndex(transport, 'index', size)
740
del transport._activity[:]
741
self.assertEqual([], transport._activity)
777
t = transport.get_transport_from_url('trace+' + self.get_url(''))
778
size = t.put_file('index', builder.finish())
779
index = btree_index.BTreeGraphIndex(t, 'index', size)
781
self.assertEqual([], t._activity)
743
783
# The entire index should have been read linearly.
744
784
self.assertEqual([('readv', 'index', [(0, size)], False, None)],
746
self.assertEqual(1514, size)
786
self.assertEqualsApproxCompressed(1488, size)
748
788
def test_validate_two_pages(self):
749
789
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
750
790
nodes = self.make_nodes(80, 2, 2)
751
791
for node in nodes:
752
792
builder.add_node(*node)
753
transport = get_transport('trace+' + self.get_url(''))
754
size = transport.put_file('index', builder.finish())
793
t = transport.get_transport_from_url('trace+' + self.get_url(''))
794
size = t.put_file('index', builder.finish())
755
795
# Root page, 2 leaf pages
756
self.assertEqual(9339, size)
757
index = btree_index.BTreeGraphIndex(transport, 'index', size)
758
del transport._activity[:]
759
self.assertEqual([], transport._activity)
796
self.assertEqualsApproxCompressed(9339, size)
797
index = btree_index.BTreeGraphIndex(t, 'index', size)
799
self.assertEqual([], t._activity)
801
rem = size - 8192 # Number of remaining bytes after second block
761
802
# The entire index should have been read linearly.
762
self.assertEqual([('readv', 'index', [(0, 4096)], False, None),
763
('readv', 'index', [(4096, 4096), (8192, 1147)], False, None)],
804
[('readv', 'index', [(0, 4096)], False, None),
805
('readv', 'index', [(4096, 4096), (8192, rem)], False, None)],
765
807
# XXX: TODO: write some badly-ordered nodes, and some pointers-to-wrong
766
808
# node and make validate find them.
768
810
def test_eq_ne(self):
769
811
# two indices are equal when constructed with the same parameters:
770
transport1 = get_transport('trace+' + self.get_url(''))
771
transport2 = get_transport(self.get_url(''))
773
btree_index.BTreeGraphIndex(transport1, 'index', None) ==
774
btree_index.BTreeGraphIndex(transport1, 'index', None))
776
btree_index.BTreeGraphIndex(transport1, 'index', 20) ==
777
btree_index.BTreeGraphIndex(transport1, 'index', 20))
779
btree_index.BTreeGraphIndex(transport1, 'index', 20) ==
780
btree_index.BTreeGraphIndex(transport2, 'index', 20))
782
btree_index.BTreeGraphIndex(transport1, 'inde1', 20) ==
783
btree_index.BTreeGraphIndex(transport1, 'inde2', 20))
785
btree_index.BTreeGraphIndex(transport1, 'index', 10) ==
786
btree_index.BTreeGraphIndex(transport1, 'index', 20))
788
btree_index.BTreeGraphIndex(transport1, 'index', None) !=
789
btree_index.BTreeGraphIndex(transport1, 'index', None))
791
btree_index.BTreeGraphIndex(transport1, 'index', 20) !=
792
btree_index.BTreeGraphIndex(transport1, 'index', 20))
794
btree_index.BTreeGraphIndex(transport1, 'index', 20) !=
795
btree_index.BTreeGraphIndex(transport2, 'index', 20))
797
btree_index.BTreeGraphIndex(transport1, 'inde1', 20) !=
798
btree_index.BTreeGraphIndex(transport1, 'inde2', 20))
800
btree_index.BTreeGraphIndex(transport1, 'index', 10) !=
801
btree_index.BTreeGraphIndex(transport1, 'index', 20))
812
t1 = transport.get_transport_from_url('trace+' + self.get_url(''))
813
t2 = self.get_transport()
815
btree_index.BTreeGraphIndex(t1, 'index', None) ==
816
btree_index.BTreeGraphIndex(t1, 'index', None))
818
btree_index.BTreeGraphIndex(t1, 'index', 20) ==
819
btree_index.BTreeGraphIndex(t1, 'index', 20))
821
btree_index.BTreeGraphIndex(t1, 'index', 20) ==
822
btree_index.BTreeGraphIndex(t2, 'index', 20))
824
btree_index.BTreeGraphIndex(t1, 'inde1', 20) ==
825
btree_index.BTreeGraphIndex(t1, 'inde2', 20))
827
btree_index.BTreeGraphIndex(t1, 'index', 10) ==
828
btree_index.BTreeGraphIndex(t1, 'index', 20))
830
btree_index.BTreeGraphIndex(t1, 'index', None) !=
831
btree_index.BTreeGraphIndex(t1, 'index', None))
833
btree_index.BTreeGraphIndex(t1, 'index', 20) !=
834
btree_index.BTreeGraphIndex(t1, 'index', 20))
836
btree_index.BTreeGraphIndex(t1, 'index', 20) !=
837
btree_index.BTreeGraphIndex(t2, 'index', 20))
839
btree_index.BTreeGraphIndex(t1, 'inde1', 20) !=
840
btree_index.BTreeGraphIndex(t1, 'inde2', 20))
842
btree_index.BTreeGraphIndex(t1, 'index', 10) !=
843
btree_index.BTreeGraphIndex(t1, 'index', 20))
845
def test_key_too_big(self):
846
# the size that matters here is the _compressed_ size of the key, so we can't
847
# do a simple character repeat.
848
bigKey = ''.join(map(repr, xrange(btree_index._PAGE_SIZE)))
849
self.assertRaises(errors.BadIndexKey,
851
nodes=[((bigKey,), 'value', ())])
803
853
def test_iter_all_only_root_no_size(self):
804
854
self.make_index(nodes=[(('key',), 'value', ())])
805
trans = get_transport('trace+' + self.get_url(''))
806
index = btree_index.BTreeGraphIndex(trans, 'index', None)
807
del trans._activity[:]
855
t = transport.get_transport_from_url('trace+' + self.get_url(''))
856
index = btree_index.BTreeGraphIndex(t, 'index', None)
808
858
self.assertEqual([(('key',), 'value')],
809
859
[x[1:] for x in index.iter_all_entries()])
810
self.assertEqual([('get', 'index')], trans._activity)
860
self.assertEqual([('get', 'index')], t._activity)
812
862
def test_iter_all_entries_reads(self):
813
863
# iterating all entries reads the header, then does a linear
1115
1163
self.assertEqual({}, parent_map)
1116
1164
self.assertEqual(set([('one',), ('two',)]), missing_keys)
1166
def test_supports_unlimited_cache(self):
1167
builder = btree_index.BTreeBuilder(reference_lists=0, key_elements=1)
1168
# We need enough nodes to cause a page split (so we have both an
1169
# internal node and a couple leaf nodes. 500 seems to be enough.)
1170
nodes = self.make_nodes(500, 1, 0)
1172
builder.add_node(*node)
1173
stream = builder.finish()
1174
trans = self.get_transport()
1175
size = trans.put_file('index', stream)
1176
index = btree_index.BTreeGraphIndex(trans, 'index', size)
1177
self.assertEqual(500, index.key_count())
1178
# We have an internal node
1179
self.assertEqual(2, len(index._row_lengths))
1180
# We have at least 2 leaf nodes
1181
self.assertTrue(index._row_lengths[-1] >= 2)
1182
self.assertIsInstance(index._leaf_node_cache, lru_cache.LRUCache)
1183
self.assertEqual(btree_index._NODE_CACHE_SIZE,
1184
index._leaf_node_cache._max_cache)
1185
self.assertIsInstance(index._internal_node_cache, fifo_cache.FIFOCache)
1186
self.assertEqual(100, index._internal_node_cache._max_cache)
1187
# No change if unlimited_cache=False is passed
1188
index = btree_index.BTreeGraphIndex(trans, 'index', size,
1189
unlimited_cache=False)
1190
self.assertIsInstance(index._leaf_node_cache, lru_cache.LRUCache)
1191
self.assertEqual(btree_index._NODE_CACHE_SIZE,
1192
index._leaf_node_cache._max_cache)
1193
self.assertIsInstance(index._internal_node_cache, fifo_cache.FIFOCache)
1194
self.assertEqual(100, index._internal_node_cache._max_cache)
1195
index = btree_index.BTreeGraphIndex(trans, 'index', size,
1196
unlimited_cache=True)
1197
self.assertIsInstance(index._leaf_node_cache, dict)
1198
self.assertIs(type(index._internal_node_cache), dict)
1199
# Exercise the lookup code
1200
entries = set(index.iter_entries([n[0] for n in nodes]))
1201
self.assertEqual(500, len(entries))
1119
1204
class TestBTreeNodes(BTreeTestCase):
1121
def restore_parser(self):
1122
btree_index._btree_serializer = self.saved_parser
1206
scenarios = btreeparser_scenarios()
1124
1208
def setUp(self):
1125
BTreeTestCase.setUp(self)
1126
self.saved_parser = btree_index._btree_serializer
1127
self.addCleanup(self.restore_parser)
1128
btree_index._btree_serializer = self.parse_btree
1209
super(TestBTreeNodes, self).setUp()
1210
self.overrideAttr(btree_index, '_btree_serializer', self.parse_btree)
1130
1212
def test_LeafNode_1_0(self):
1131
1213
node_bytes = ("type=leaf\n"