32
32
from bzrlib.tests import (
33
33
TestCaseWithTransport,
36
from bzrlib.tests import (
41
load_tests = scenarios.load_tests_apply_scenarios
44
def btreeparser_scenarios():
36
split_suite_by_condition,
40
def load_tests(standard_tests, module, loader):
41
# parameterise the TestBTreeNodes tests
42
node_tests, others = split_suite_by_condition(standard_tests,
43
condition_isinstance(TestBTreeNodes))
45
44
import bzrlib._btree_serializer_py as py_module
46
45
scenarios = [('python', {'parse_btree': py_module})]
47
46
if compiled_btreeparser_feature.available():
48
scenarios.append(('C',
49
{'parse_btree': compiled_btreeparser_feature.module}))
53
compiled_btreeparser_feature = features.ModuleAvailableFeature(
54
'bzrlib._btree_serializer_pyx')
47
scenarios.append(('C', {'parse_btree':
48
compiled_btreeparser_feature.module}))
49
return multiply_tests(node_tests, scenarios, others)
52
compiled_btreeparser_feature = tests.ModuleAvailableFeature(
53
'bzrlib._btree_serializer_pyx')
57
56
class BTreeTestCase(TestCaseWithTransport):
62
super(BTreeTestCase, self).setUp()
61
TestCaseWithTransport.setUp(self)
63
62
self.overrideAttr(btree_index, '_RESERVED_HEADER_BYTES', 100)
65
64
def make_nodes(self, count, key_elements, reference_lists):
103
102
self.overrideAttr(btree_index, '_PAGE_SIZE')
104
103
btree_index._PAGE_SIZE = 2048
106
def assertEqualApproxCompressed(self, expected, actual, slop=6):
107
"""Check a count of compressed bytes is approximately as expected
109
Relying on compressed length being stable even with fixed inputs is
110
slightly bogus, but zlib is stable enough that this mostly works.
112
if not expected - slop < actual < expected + slop:
113
self.fail("Expected around %d bytes compressed but got %d" %
117
106
class TestBTreeBuilder(BTreeTestCase):
618
607
for key, value, references in nodes:
619
608
builder.add_node(key, value, references)
620
609
stream = builder.finish()
621
trans = transport.get_transport_from_url('trace+' + self.get_url())
610
trans = transport.get_transport('trace+' + self.get_url())
622
611
size = trans.put_file('index', stream)
623
612
return btree_index.BTreeGraphIndex(trans, 'index', size)
659
648
self.assertEqual(0, len(index._leaf_node_cache))
661
650
def test_trivial_constructor(self):
662
t = transport.get_transport_from_url('trace+' + self.get_url(''))
651
t = transport.get_transport('trace+' + self.get_url(''))
663
652
index = btree_index.BTreeGraphIndex(t, 'index', None)
664
653
# Checks the page size at load, but that isn't logged yet.
665
654
self.assertEqual([], t._activity)
667
656
def test_with_size_constructor(self):
668
t = transport.get_transport_from_url('trace+' + self.get_url(''))
657
t = transport.get_transport('trace+' + self.get_url(''))
669
658
index = btree_index.BTreeGraphIndex(t, 'index', 1)
670
659
# Checks the page size at load, but that isn't logged yet.
671
660
self.assertEqual([], t._activity)
673
662
def test_empty_key_count_no_size(self):
674
663
builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
675
t = transport.get_transport_from_url('trace+' + self.get_url(''))
664
t = transport.get_transport('trace+' + self.get_url(''))
676
665
t.put_file('index', builder.finish())
677
666
index = btree_index.BTreeGraphIndex(t, 'index', None)
678
667
del t._activity[:]
686
675
def test_empty_key_count(self):
687
676
builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
688
t = transport.get_transport_from_url('trace+' + self.get_url(''))
677
t = transport.get_transport('trace+' + self.get_url(''))
689
678
size = t.put_file('index', builder.finish())
690
679
self.assertEqual(72, size)
691
680
index = btree_index.BTreeGraphIndex(t, 'index', size)
701
690
nodes = self.make_nodes(35, 2, 2)
702
691
for node in nodes:
703
692
builder.add_node(*node)
704
t = transport.get_transport_from_url('trace+' + self.get_url(''))
693
t = transport.get_transport('trace+' + self.get_url(''))
705
694
size = t.put_file('index', builder.finish())
706
695
index = btree_index.BTreeGraphIndex(t, 'index', size)
707
696
del t._activity[:]
710
699
# The entire index should have been read, as it is one page long.
711
700
self.assertEqual([('readv', 'index', [(0, size)], False, None)],
713
self.assertEqualApproxCompressed(1173, size)
702
self.assertEqual(1173, size)
715
704
def test_with_offset_no_size(self):
716
705
index = self.make_index_with_offset(key_elements=1, ref_lists=1,
734
723
def test__read_nodes_no_size_one_page_reads_once(self):
735
724
self.make_index(nodes=[(('key',), 'value', ())])
736
trans = transport.get_transport_from_url('trace+' + self.get_url())
725
trans = transport.get_transport('trace+' + self.get_url())
737
726
index = btree_index.BTreeGraphIndex(trans, 'index', None)
738
727
del trans._activity[:]
739
728
nodes = dict(index._read_nodes([0]))
747
736
index.key_count()
748
737
num_pages = index._row_offsets[-1]
749
738
# Reopen with a traced transport and no size
750
trans = transport.get_transport_from_url('trace+' + self.get_url())
739
trans = transport.get_transport('trace+' + self.get_url())
751
740
index = btree_index.BTreeGraphIndex(trans, 'index', None)
752
741
del trans._activity[:]
753
742
nodes = dict(index._read_nodes([0]))
758
747
nodes = self.make_nodes(160, 2, 2)
759
748
for node in nodes:
760
749
builder.add_node(*node)
761
t = transport.get_transport_from_url('trace+' + self.get_url(''))
750
t = transport.get_transport('trace+' + self.get_url(''))
762
751
size = t.put_file('index', builder.finish())
763
self.assertEqualApproxCompressed(17692, size)
752
self.assertEqual(17692, size)
764
753
index = btree_index.BTreeGraphIndex(t, 'index', size)
765
754
del t._activity[:]
766
755
self.assertEqual([], t._activity)
774
763
nodes = self.make_nodes(45, 2, 2)
775
764
for node in nodes:
776
765
builder.add_node(*node)
777
t = transport.get_transport_from_url('trace+' + self.get_url(''))
766
t = transport.get_transport('trace+' + self.get_url(''))
778
767
size = t.put_file('index', builder.finish())
779
768
index = btree_index.BTreeGraphIndex(t, 'index', size)
780
769
del t._activity[:]
783
772
# The entire index should have been read linearly.
784
773
self.assertEqual([('readv', 'index', [(0, size)], False, None)],
786
self.assertEqualApproxCompressed(1488, size)
775
self.assertEqual(1488, size)
788
777
def test_validate_two_pages(self):
789
778
builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
790
779
nodes = self.make_nodes(80, 2, 2)
791
780
for node in nodes:
792
781
builder.add_node(*node)
793
t = transport.get_transport_from_url('trace+' + self.get_url(''))
782
t = transport.get_transport('trace+' + self.get_url(''))
794
783
size = t.put_file('index', builder.finish())
795
784
# Root page, 2 leaf pages
796
self.assertEqualApproxCompressed(9339, size)
785
self.assertEqual(9339, size)
797
786
index = btree_index.BTreeGraphIndex(t, 'index', size)
798
787
del t._activity[:]
799
788
self.assertEqual([], t._activity)
801
rem = size - 8192 # Number of remaining bytes after second block
802
790
# The entire index should have been read linearly.
803
791
self.assertEqual(
804
792
[('readv', 'index', [(0, 4096)], False, None),
805
('readv', 'index', [(4096, 4096), (8192, rem)], False, None)],
793
('readv', 'index', [(4096, 4096), (8192, 1147)], False, None)],
807
795
# XXX: TODO: write some badly-ordered nodes, and some pointers-to-wrong
808
796
# node and make validate find them.
810
798
def test_eq_ne(self):
811
799
# two indices are equal when constructed with the same parameters:
812
t1 = transport.get_transport_from_url('trace+' + self.get_url(''))
813
t2 = self.get_transport()
800
t1 = transport.get_transport('trace+' + self.get_url(''))
801
t2 = transport.get_transport(self.get_url(''))
815
803
btree_index.BTreeGraphIndex(t1, 'index', None) ==
816
804
btree_index.BTreeGraphIndex(t1, 'index', None))
842
830
btree_index.BTreeGraphIndex(t1, 'index', 10) !=
843
831
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', ())])
853
833
def test_iter_all_only_root_no_size(self):
854
834
self.make_index(nodes=[(('key',), 'value', ())])
855
t = transport.get_transport_from_url('trace+' + self.get_url(''))
835
t = transport.get_transport('trace+' + self.get_url(''))
856
836
index = btree_index.BTreeGraphIndex(t, 'index', None)
857
837
del t._activity[:]
858
838
self.assertEqual([(('key',), 'value')],
869
849
nodes = self.make_nodes(10000, 2, 2)
870
850
for node in nodes:
871
851
builder.add_node(*node)
872
t = transport.get_transport_from_url('trace+' + self.get_url(''))
852
t = transport.get_transport('trace+' + self.get_url(''))
873
853
size = t.put_file('index', builder.finish())
854
self.assertEqual(1303220, size, 'number of expected bytes in the'
874
856
page_size = btree_index._PAGE_SIZE
876
858
index = btree_index.BTreeGraphIndex(t, 'index', size)
892
874
# The entire index should have been read
893
875
total_pages = sum(index._row_lengths)
894
876
self.assertEqual(total_pages, index._row_offsets[-1])
895
self.assertEqualApproxCompressed(1303220, size)
877
self.assertEqual(1303220, size)
896
878
# The start of the leaves
897
879
first_byte = index._row_offsets[-2] * page_size
898
880
readv_request = []
899
881
for offset in range(first_byte, size, page_size):
900
882
readv_request.append((offset, page_size))
901
883
# The last page is truncated
902
readv_request[-1] = (readv_request[-1][0], size % page_size)
884
readv_request[-1] = (readv_request[-1][0], 1303220 % page_size)
903
885
expected = [('readv', 'index', [(0, page_size)], False, None),
904
886
('readv', 'index', readv_request, False, None)]
905
887
if expected != t._activity:
906
888
self.assertEqualDiff(pprint.pformat(expected),
907
pprint.pformat(t._activity))
889
pprint.pformat(transport._activity))
909
891
def _test_iter_entries_references_resolved(self):
910
892
index = self.make_index(1, nodes=[
922
904
nodes = self.make_nodes(160, 2, 2)
923
905
for node in nodes:
924
906
builder.add_node(*node)
925
t = transport.get_transport_from_url('trace+' + self.get_url(''))
907
t = transport.get_transport('trace+' + self.get_url(''))
926
908
size = t.put_file('index', builder.finish())
928
910
index = btree_index.BTreeGraphIndex(t, 'index', size)
1171
1153
for node in nodes:
1172
1154
builder.add_node(*node)
1173
1155
stream = builder.finish()
1174
trans = self.get_transport()
1156
trans = transport.get_transport(self.get_url())
1175
1157
size = trans.put_file('index', stream)
1176
1158
index = btree_index.BTreeGraphIndex(trans, 'index', size)
1177
1159
self.assertEqual(500, index.key_count())
1204
1186
class TestBTreeNodes(BTreeTestCase):
1206
scenarios = btreeparser_scenarios()
1208
1188
def setUp(self):
1209
super(TestBTreeNodes, self).setUp()
1189
BTreeTestCase.setUp(self)
1210
1190
self.overrideAttr(btree_index, '_btree_serializer', self.parse_btree)
1212
1192
def test_LeafNode_1_0(self):
1371
1351
BTreeGraphIndex with the recommended information.
1373
1353
index = btree_index.BTreeGraphIndex(
1374
transport.get_transport_from_url('memory:///'),
1375
'test-index', size=size)
1354
transport.get_transport('memory:///'), 'test-index', size=size)
1376
1355
if recommended_pages is not None:
1377
1356
index._recommended_pages = recommended_pages