~bzr-pqm/bzr/bzr.dev

4593.4.5 by John Arbash Meinel
Start adding some tests.
1
# Copyright (C) 2008, 2009 Canonical Ltd
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
2
#
3
# This program is free software; you can redistribute it and/or modify
3641.3.29 by John Arbash Meinel
Cleanup the copyright headers
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.
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
16
#
17
18
"""Tests for btree indices."""
19
3641.3.6 by John Arbash Meinel
Dropping the number of nodes to 100k from 200k
20
import pprint
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
21
import zlib
22
23
from bzrlib import (
24
    btree_index,
25
    errors,
4634.71.1 by John Arbash Meinel
Work around bug #402623 by allowing BTreeGraphIndex(...,unlimited_cache=True).
26
    fifo_cache,
27
    lru_cache,
4593.4.6 by John Arbash Meinel
Add a test that we can walk some of the keys on a given page
28
    osutils,
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
29
    tests,
30
    )
31
from bzrlib.tests import (
32
    TestCaseWithTransport,
33
    condition_isinstance,
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
34
    multiply_tests,
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
35
    split_suite_by_condition,
36
    )
37
from bzrlib.transport import get_transport
38
39
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))
3641.3.30 by John Arbash Meinel
Rename _parse_btree to _btree_serializer
44
    import bzrlib._btree_serializer_py as py_module
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
45
    scenarios = [('python', {'parse_btree': py_module})]
4913.2.20 by John Arbash Meinel
Change all of the compiled_foo to compiled_foo_feature
46
    if compiled_btreeparser_feature.available():
47
        scenarios.append(('C', {'parse_btree':
48
                                compiled_btreeparser_feature.module}))
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
49
    return multiply_tests(node_tests, scenarios, others)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
50
51
4913.2.20 by John Arbash Meinel
Change all of the compiled_foo to compiled_foo_feature
52
compiled_btreeparser_feature = tests.ModuleAvailableFeature(
53
                                'bzrlib._btree_serializer_pyx')
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
54
55
56
class BTreeTestCase(TestCaseWithTransport):
57
    # test names here are suffixed by the key length and reference list count
58
    # that they test.
59
60
    def setUp(self):
61
        TestCaseWithTransport.setUp(self)
4985.1.5 by Vincent Ladeuil
Deploying the new overrideAttr facility further reduces the complexity
62
        self.overrideAttr(btree_index, '_RESERVED_HEADER_BYTES', 100)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
63
64
    def make_nodes(self, count, key_elements, reference_lists):
65
        """Generate count*key_elements sample nodes."""
66
        keys = []
67
        for prefix_pos in range(key_elements):
68
            if key_elements - 1:
69
                prefix = (str(prefix_pos) * 40,)
70
            else:
71
                prefix = ()
3641.3.6 by John Arbash Meinel
Dropping the number of nodes to 100k from 200k
72
            for pos in xrange(count):
73
                # TODO: This creates odd keys. When count == 100,000, it
74
                #       creates a 240 byte key
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
75
                key = prefix + (str(pos) * 40,)
76
                value = "value:%s" % pos
77
                if reference_lists:
78
                    # generate some references
79
                    refs = []
80
                    for list_pos in range(reference_lists):
81
                        # as many keys in each list as its index + the key depth
82
                        # mod 2 - this generates both 0 length lists and
83
                        # ones slightly longer than the number of lists.
3641.3.6 by John Arbash Meinel
Dropping the number of nodes to 100k from 200k
84
                        # It also ensures we have non homogeneous lists.
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
85
                        refs.append([])
86
                        for ref_pos in range(list_pos + pos % 2):
87
                            if pos % 2:
88
                                # refer to a nearby key
89
                                refs[-1].append(prefix + ("ref" + str(pos - 1) * 40,))
90
                            else:
91
                                # serial of this ref in the ref list
92
                                refs[-1].append(prefix + ("ref" + str(ref_pos) * 40,))
93
                        refs[-1] = tuple(refs[-1])
94
                    refs = tuple(refs)
95
                else:
96
                    refs = ()
97
                keys.append((key, value, refs))
98
        return keys
99
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
100
    def shrink_page_size(self):
101
        """Shrink the default page size so that less fits in a page."""
4985.1.5 by Vincent Ladeuil
Deploying the new overrideAttr facility further reduces the complexity
102
        self.overrideAttr(btree_index, '_PAGE_SIZE')
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
103
        btree_index._PAGE_SIZE = 2048
104
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
105
106
class TestBTreeBuilder(BTreeTestCase):
107
4744.2.7 by John Arbash Meinel
Add .clear_cache() members to GraphIndexBuilder and BTreeBuilder.
108
    def test_clear_cache(self):
109
        builder = btree_index.BTreeBuilder(reference_lists=0, key_elements=1)
110
        # This is a no-op, but we need the api to be consistent with other
111
        # BTreeGraphIndex apis.
112
        builder.clear_cache()
113
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
114
    def test_empty_1_0(self):
115
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
116
        # NamedTemporaryFile dies on builder.finish().read(). weird.
117
        temp_file = builder.finish()
118
        content = temp_file.read()
119
        del temp_file
120
        self.assertEqual(
3641.3.3 by John Arbash Meinel
Change the header to indicate these indexes are
121
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=0\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
122
            "row_lengths=\n",
123
            content)
124
125
    def test_empty_2_1(self):
126
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=1)
127
        # NamedTemporaryFile dies on builder.finish().read(). weird.
128
        temp_file = builder.finish()
129
        content = temp_file.read()
130
        del temp_file
131
        self.assertEqual(
3641.3.3 by John Arbash Meinel
Change the header to indicate these indexes are
132
            "B+Tree Graph Index 2\nnode_ref_lists=1\nkey_elements=2\nlen=0\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
133
            "row_lengths=\n",
134
            content)
135
136
    def test_root_leaf_1_0(self):
137
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
138
        nodes = self.make_nodes(5, 1, 0)
139
        for node in nodes:
140
            builder.add_node(*node)
141
        # NamedTemporaryFile dies on builder.finish().read(). weird.
142
        temp_file = builder.finish()
143
        content = temp_file.read()
144
        del temp_file
4771.3.1 by John Arbash Meinel
We don't have to pad 'short' records.
145
        self.assertEqual(131, len(content))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
146
        self.assertEqual(
3641.3.3 by John Arbash Meinel
Change the header to indicate these indexes are
147
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=5\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
148
            "row_lengths=1\n",
149
            content[:73])
150
        node_content = content[73:]
151
        node_bytes = zlib.decompress(node_content)
152
        expected_node = ("type=leaf\n"
153
            "0000000000000000000000000000000000000000\x00\x00value:0\n"
154
            "1111111111111111111111111111111111111111\x00\x00value:1\n"
155
            "2222222222222222222222222222222222222222\x00\x00value:2\n"
156
            "3333333333333333333333333333333333333333\x00\x00value:3\n"
157
            "4444444444444444444444444444444444444444\x00\x00value:4\n")
158
        self.assertEqual(expected_node, node_bytes)
159
160
    def test_root_leaf_2_2(self):
161
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
162
        nodes = self.make_nodes(5, 2, 2)
163
        for node in nodes:
164
            builder.add_node(*node)
165
        # NamedTemporaryFile dies on builder.finish().read(). weird.
166
        temp_file = builder.finish()
167
        content = temp_file.read()
168
        del temp_file
4771.3.1 by John Arbash Meinel
We don't have to pad 'short' records.
169
        self.assertEqual(238, len(content))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
170
        self.assertEqual(
3641.3.3 by John Arbash Meinel
Change the header to indicate these indexes are
171
            "B+Tree Graph Index 2\nnode_ref_lists=2\nkey_elements=2\nlen=10\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
172
            "row_lengths=1\n",
173
            content[:74])
174
        node_content = content[74:]
175
        node_bytes = zlib.decompress(node_content)
176
        expected_node = (
177
            "type=leaf\n"
178
            "0000000000000000000000000000000000000000\x000000000000000000000000000000000000000000\x00\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:0\n"
179
            "0000000000000000000000000000000000000000\x001111111111111111111111111111111111111111\x000000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\r0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:1\n"
180
            "0000000000000000000000000000000000000000\x002222222222222222222222222222222222222222\x00\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:2\n"
181
            "0000000000000000000000000000000000000000\x003333333333333333333333333333333333333333\x000000000000000000000000000000000000000000\x00ref2222222222222222222222222222222222222222\t0000000000000000000000000000000000000000\x00ref2222222222222222222222222222222222222222\r0000000000000000000000000000000000000000\x00ref2222222222222222222222222222222222222222\x00value:3\n"
182
            "0000000000000000000000000000000000000000\x004444444444444444444444444444444444444444\x00\t0000000000000000000000000000000000000000\x00ref0000000000000000000000000000000000000000\x00value:4\n"
183
            "1111111111111111111111111111111111111111\x000000000000000000000000000000000000000000\x00\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:0\n"
184
            "1111111111111111111111111111111111111111\x001111111111111111111111111111111111111111\x001111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\r1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:1\n"
185
            "1111111111111111111111111111111111111111\x002222222222222222222222222222222222222222\x00\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:2\n"
186
            "1111111111111111111111111111111111111111\x003333333333333333333333333333333333333333\x001111111111111111111111111111111111111111\x00ref2222222222222222222222222222222222222222\t1111111111111111111111111111111111111111\x00ref2222222222222222222222222222222222222222\r1111111111111111111111111111111111111111\x00ref2222222222222222222222222222222222222222\x00value:3\n"
187
            "1111111111111111111111111111111111111111\x004444444444444444444444444444444444444444\x00\t1111111111111111111111111111111111111111\x00ref0000000000000000000000000000000000000000\x00value:4\n"
188
            ""
189
            )
190
        self.assertEqual(expected_node, node_bytes)
191
192
    def test_2_leaves_1_0(self):
193
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
194
        nodes = self.make_nodes(400, 1, 0)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
195
        for node in nodes:
196
            builder.add_node(*node)
197
        # NamedTemporaryFile dies on builder.finish().read(). weird.
198
        temp_file = builder.finish()
199
        content = temp_file.read()
200
        del temp_file
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
201
        self.assertEqual(9283, len(content))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
202
        self.assertEqual(
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
203
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=400\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
204
            "row_lengths=1,2\n",
205
            content[:77])
206
        root = content[77:4096]
207
        leaf1 = content[4096:8192]
208
        leaf2 = content[8192:]
209
        root_bytes = zlib.decompress(root)
210
        expected_root = (
211
            "type=internal\n"
212
            "offset=0\n"
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
213
            ) + ("307" * 40) + "\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
214
        self.assertEqual(expected_root, root_bytes)
215
        # We already know serialisation works for leaves, check key selection:
216
        leaf1_bytes = zlib.decompress(leaf1)
217
        sorted_node_keys = sorted(node[0] for node in nodes)
218
        node = btree_index._LeafNode(leaf1_bytes, 1, 0)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
219
        self.assertEqual(231, len(node.keys))
220
        self.assertEqual(sorted_node_keys[:231], sorted(node.keys))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
221
        leaf2_bytes = zlib.decompress(leaf2)
222
        node = btree_index._LeafNode(leaf2_bytes, 1, 0)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
223
        self.assertEqual(400 - 231, len(node.keys))
224
        self.assertEqual(sorted_node_keys[231:], sorted(node.keys))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
225
226
    def test_last_page_rounded_1_layer(self):
227
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
228
        nodes = self.make_nodes(10, 1, 0)
229
        for node in nodes:
230
            builder.add_node(*node)
231
        # NamedTemporaryFile dies on builder.finish().read(). weird.
232
        temp_file = builder.finish()
233
        content = temp_file.read()
234
        del temp_file
4771.3.1 by John Arbash Meinel
We don't have to pad 'short' records.
235
        self.assertEqual(155, len(content))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
236
        self.assertEqual(
3641.3.3 by John Arbash Meinel
Change the header to indicate these indexes are
237
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=10\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
238
            "row_lengths=1\n",
239
            content[:74])
240
        # Check thelast page is well formed
241
        leaf2 = content[74:]
242
        leaf2_bytes = zlib.decompress(leaf2)
243
        node = btree_index._LeafNode(leaf2_bytes, 1, 0)
244
        self.assertEqual(10, len(node.keys))
245
        sorted_node_keys = sorted(node[0] for node in nodes)
246
        self.assertEqual(sorted_node_keys, sorted(node.keys))
247
248
    def test_last_page_not_rounded_2_layer(self):
249
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
250
        nodes = self.make_nodes(400, 1, 0)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
251
        for node in nodes:
252
            builder.add_node(*node)
253
        # NamedTemporaryFile dies on builder.finish().read(). weird.
254
        temp_file = builder.finish()
255
        content = temp_file.read()
256
        del temp_file
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
257
        self.assertEqual(9283, len(content))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
258
        self.assertEqual(
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
259
            "B+Tree Graph Index 2\nnode_ref_lists=0\nkey_elements=1\nlen=400\n"
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
260
            "row_lengths=1,2\n",
261
            content[:77])
3641.3.30 by John Arbash Meinel
Rename _parse_btree to _btree_serializer
262
        # Check the last page is well formed
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
263
        leaf2 = content[8192:]
264
        leaf2_bytes = zlib.decompress(leaf2)
265
        node = btree_index._LeafNode(leaf2_bytes, 1, 0)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
266
        self.assertEqual(400 - 231, len(node.keys))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
267
        sorted_node_keys = sorted(node[0] for node in nodes)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
268
        self.assertEqual(sorted_node_keys[231:], sorted(node.keys))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
269
270
    def test_three_level_tree_details(self):
271
        # The left most pointer in the second internal node in a row should
272
        # pointer to the second node that the internal node is for, _not_
273
        # the first, otherwise the first node overlaps with the last node of
274
        # the prior internal node on that row.
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
275
        self.shrink_page_size()
276
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
277
        # 40K nodes is enough to create a two internal nodes on the second
278
        # level, with a 2K page size
279
        nodes = self.make_nodes(20000, 2, 2)
3641.3.5 by John Arbash Meinel
For iter_all and three_level tests adjust spill-at.
280
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
281
        for node in nodes:
282
            builder.add_node(*node)
283
        transport = get_transport('trace+' + self.get_url(''))
3641.3.7 by John Arbash Meinel
Make it easier to profile the btree writer time
284
        size = transport.put_file('index', self.time(builder.finish))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
285
        del builder
286
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
287
        # Seed the metadata, we're using internal calls now.
288
        index.key_count()
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)
306
307
    def test_2_leaves_2_2(self):
308
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
309
        nodes = self.make_nodes(100, 2, 2)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
310
        for node in nodes:
311
            builder.add_node(*node)
312
        # NamedTemporaryFile dies on builder.finish().read(). weird.
313
        temp_file = builder.finish()
314
        content = temp_file.read()
315
        del temp_file
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
316
        self.assertEqual(12643, len(content))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
317
        self.assertEqual(
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
318
            "B+Tree Graph Index 2\nnode_ref_lists=2\nkey_elements=2\nlen=200\n"
319
            "row_lengths=1,3\n",
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
320
            content[:77])
321
        root = content[77:4096]
322
        leaf1 = content[4096:8192]
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
323
        leaf2 = content[8192:12288]
324
        leaf3 = content[12288:]
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
325
        root_bytes = zlib.decompress(root)
326
        expected_root = (
327
            "type=internal\n"
328
            "offset=0\n"
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
329
            + ("0" * 40) + "\x00" + ("91" * 40) + "\n"
330
            + ("1" * 40) + "\x00" + ("81" * 40) + "\n"
331
            )
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
332
        self.assertEqual(expected_root, root_bytes)
333
        # We assume the other leaf nodes have been written correctly - layering
334
        # FTW.
335
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
341
        # predictably.
342
        self.assertEqual(1, len(builder._nodes))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
343
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
344
        builder.add_node(*nodes[1])
345
        self.assertEqual(0, len(builder._nodes))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
346
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
347
        self.assertEqual(1, len(builder._backing_indices))
348
        self.assertEqual(2, builder._backing_indices[0].key_count())
349
        # now back to memory
350
        builder.add_node(*nodes[2])
351
        self.assertEqual(1, len(builder._nodes))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
352
        self.assertIs(None, builder._nodes_by_key)
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
353
        # And spills to a second backing index combing all
354
        builder.add_node(*nodes[3])
355
        self.assertEqual(0, len(builder._nodes))
356
        self.assertIs(None, builder._nodes_by_key)
357
        self.assertEqual(2, len(builder._backing_indices))
358
        self.assertEqual(None, builder._backing_indices[0])
359
        self.assertEqual(4, builder._backing_indices[1].key_count())
360
        # The next spills to the 2-len slot
361
        builder.add_node(*nodes[4])
362
        builder.add_node(*nodes[5])
363
        self.assertEqual(0, len(builder._nodes))
364
        self.assertIs(None, builder._nodes_by_key)
365
        self.assertEqual(2, len(builder._backing_indices))
366
        self.assertEqual(2, builder._backing_indices[0].key_count())
367
        self.assertEqual(4, builder._backing_indices[1].key_count())
368
        # Next spill combines
369
        builder.add_node(*nodes[6])
370
        builder.add_node(*nodes[7])
371
        self.assertEqual(3, len(builder._backing_indices))
372
        self.assertEqual(None, builder._backing_indices[0])
373
        self.assertEqual(None, builder._backing_indices[1])
374
        self.assertEqual(8, builder._backing_indices[2].key_count())
375
        # And so forth - counting up in binary.
376
        builder.add_node(*nodes[8])
377
        builder.add_node(*nodes[9])
378
        self.assertEqual(3, len(builder._backing_indices))
379
        self.assertEqual(2, builder._backing_indices[0].key_count())
380
        self.assertEqual(None, builder._backing_indices[1])
381
        self.assertEqual(8, builder._backing_indices[2].key_count())
382
        builder.add_node(*nodes[10])
383
        builder.add_node(*nodes[11])
384
        self.assertEqual(3, len(builder._backing_indices))
385
        self.assertEqual(None, builder._backing_indices[0])
386
        self.assertEqual(4, builder._backing_indices[1].key_count())
387
        self.assertEqual(8, builder._backing_indices[2].key_count())
388
        builder.add_node(*nodes[12])
389
        # Test that memory and disk are both used for query methods; and that
390
        # None is skipped over happily.
391
        self.assertEqual([(builder,) + node for node in sorted(nodes[:13])],
392
            list(builder.iter_all_entries()))
393
        # Two nodes - one memory one disk
394
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
395
            set(builder.iter_entries([nodes[12][0], nodes[11][0]])))
396
        self.assertEqual(13, builder.key_count())
397
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
398
            set(builder.iter_entries_prefix([nodes[12][0], nodes[11][0]])))
399
        builder.add_node(*nodes[13])
400
        self.assertEqual(3, len(builder._backing_indices))
401
        self.assertEqual(2, builder._backing_indices[0].key_count())
402
        self.assertEqual(4, builder._backing_indices[1].key_count())
403
        self.assertEqual(8, builder._backing_indices[2].key_count())
404
        builder.add_node(*nodes[14])
405
        builder.add_node(*nodes[15])
406
        self.assertEqual(4, len(builder._backing_indices))
407
        self.assertEqual(None, builder._backing_indices[0])
408
        self.assertEqual(None, builder._backing_indices[1])
409
        self.assertEqual(None, builder._backing_indices[2])
410
        self.assertEqual(16, builder._backing_indices[3].key_count())
411
        # Now finish, and check we got a correctly ordered tree
412
        transport = self.get_transport('')
413
        size = transport.put_file('index', builder.finish())
414
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
415
        nodes = list(index.iter_all_entries())
416
        self.assertEqual(sorted(nodes), nodes)
417
        self.assertEqual(16, len(nodes))
418
419
    def test_spill_index_stress_1_1_no_combine(self):
420
        builder = btree_index.BTreeBuilder(key_elements=1, spill_at=2)
4168.3.6 by John Arbash Meinel
Add 'combine_backing_indices' as a flag for GraphIndex.set_optimize().
421
        builder.set_optimize(for_size=False, combine_backing_indices=False)
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
422
        nodes = [node[0:2] for node in self.make_nodes(16, 1, 0)]
423
        builder.add_node(*nodes[0])
424
        # Test the parts of the index that take up memory are doing so
425
        # predictably.
426
        self.assertEqual(1, len(builder._nodes))
427
        self.assertIs(None, builder._nodes_by_key)
428
        builder.add_node(*nodes[1])
429
        self.assertEqual(0, len(builder._nodes))
430
        self.assertIs(None, builder._nodes_by_key)
431
        self.assertEqual(1, len(builder._backing_indices))
432
        self.assertEqual(2, builder._backing_indices[0].key_count())
433
        # now back to memory
434
        builder.add_node(*nodes[2])
435
        self.assertEqual(1, len(builder._nodes))
436
        self.assertIs(None, builder._nodes_by_key)
437
        # And spills to a second backing index but doesn't combine
438
        builder.add_node(*nodes[3])
439
        self.assertEqual(0, len(builder._nodes))
440
        self.assertIs(None, builder._nodes_by_key)
441
        self.assertEqual(2, len(builder._backing_indices))
4168.3.5 by John Arbash Meinel
Check that setting _combine_spilled_indices has the expected effect.
442
        for backing_index in builder._backing_indices:
443
            self.assertEqual(2, backing_index.key_count())
444
        # The next spills to the 3rd slot
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
445
        builder.add_node(*nodes[4])
446
        builder.add_node(*nodes[5])
447
        self.assertEqual(0, len(builder._nodes))
448
        self.assertIs(None, builder._nodes_by_key)
4168.3.5 by John Arbash Meinel
Check that setting _combine_spilled_indices has the expected effect.
449
        self.assertEqual(3, len(builder._backing_indices))
450
        for backing_index in builder._backing_indices:
451
            self.assertEqual(2, backing_index.key_count())
452
        # Now spill a few more, and check that we don't combine
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
453
        builder.add_node(*nodes[6])
454
        builder.add_node(*nodes[7])
455
        builder.add_node(*nodes[8])
456
        builder.add_node(*nodes[9])
457
        builder.add_node(*nodes[10])
458
        builder.add_node(*nodes[11])
459
        builder.add_node(*nodes[12])
4168.3.5 by John Arbash Meinel
Check that setting _combine_spilled_indices has the expected effect.
460
        self.assertEqual(6, len(builder._backing_indices))
461
        for backing_index in builder._backing_indices:
462
            self.assertEqual(2, backing_index.key_count())
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
463
        # Test that memory and disk are both used for query methods; and that
464
        # None is skipped over happily.
465
        self.assertEqual([(builder,) + node for node in sorted(nodes[:13])],
466
            list(builder.iter_all_entries()))
467
        # Two nodes - one memory one disk
468
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
469
            set(builder.iter_entries([nodes[12][0], nodes[11][0]])))
470
        self.assertEqual(13, builder.key_count())
471
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
472
            set(builder.iter_entries_prefix([nodes[12][0], nodes[11][0]])))
473
        builder.add_node(*nodes[13])
474
        builder.add_node(*nodes[14])
475
        builder.add_node(*nodes[15])
4168.3.5 by John Arbash Meinel
Check that setting _combine_spilled_indices has the expected effect.
476
        self.assertEqual(8, len(builder._backing_indices))
477
        for backing_index in builder._backing_indices:
478
            self.assertEqual(2, backing_index.key_count())
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
479
        # Now finish, and check we got a correctly ordered tree
480
        transport = self.get_transport('')
481
        size = transport.put_file('index', builder.finish())
482
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
483
        nodes = list(index.iter_all_entries())
484
        self.assertEqual(sorted(nodes), nodes)
485
        self.assertEqual(16, len(nodes))
486
3777.5.3 by John Arbash Meinel
Add Builder.set_optimize(for_size=True) for GraphIndexBuilder and BTreeBuilder.
487
    def test_set_optimize(self):
488
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
489
        builder.set_optimize(for_size=True)
490
        self.assertTrue(builder._optimize_for_size)
491
        builder.set_optimize(for_size=False)
492
        self.assertFalse(builder._optimize_for_size)
4168.3.6 by John Arbash Meinel
Add 'combine_backing_indices' as a flag for GraphIndex.set_optimize().
493
        # test that we can set combine_backing_indices without effecting
494
        # _optimize_for_size
495
        obj = object()
496
        builder._optimize_for_size = obj
497
        builder.set_optimize(combine_backing_indices=False)
498
        self.assertFalse(builder._combine_backing_indices)
499
        self.assertIs(obj, builder._optimize_for_size)
500
        builder.set_optimize(combine_backing_indices=True)
501
        self.assertTrue(builder._combine_backing_indices)
502
        self.assertIs(obj, builder._optimize_for_size)
3777.5.3 by John Arbash Meinel
Add Builder.set_optimize(for_size=True) for GraphIndexBuilder and BTreeBuilder.
503
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
504
    def test_spill_index_stress_2_2(self):
505
        # test that references and longer keys don't confuse things.
506
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2,
507
            spill_at=2)
508
        nodes = self.make_nodes(16, 2, 2)
509
        builder.add_node(*nodes[0])
510
        # Test the parts of the index that take up memory are doing so
511
        # predictably.
3644.2.1 by John Arbash Meinel
Change the IndexBuilders to not generate the nodes_by_key unless needed.
512
        self.assertEqual(1, len(builder._nodes))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
513
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
514
        builder.add_node(*nodes[1])
515
        self.assertEqual(0, len(builder._nodes))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
516
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
517
        self.assertEqual(1, len(builder._backing_indices))
518
        self.assertEqual(2, builder._backing_indices[0].key_count())
519
        # now back to memory
3644.2.6 by John Arbash Meinel
Restore the exact old tests, only assert that
520
        old = dict(builder._get_nodes_by_key()) #Build up the nodes by key dict
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
521
        builder.add_node(*nodes[2])
3644.2.1 by John Arbash Meinel
Change the IndexBuilders to not generate the nodes_by_key unless needed.
522
        self.assertEqual(1, len(builder._nodes))
3644.2.6 by John Arbash Meinel
Restore the exact old tests, only assert that
523
        self.assertIsNot(None, builder._nodes_by_key)
524
        self.assertNotEqual({}, builder._nodes_by_key)
525
        # We should have a new entry
526
        self.assertNotEqual(old, builder._nodes_by_key)
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
527
        # And spills to a second backing index combing all
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
528
        builder.add_node(*nodes[3])
529
        self.assertEqual(0, len(builder._nodes))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
530
        self.assertIs(None, builder._nodes_by_key)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
531
        self.assertEqual(2, len(builder._backing_indices))
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
532
        self.assertEqual(None, builder._backing_indices[0])
533
        self.assertEqual(4, builder._backing_indices[1].key_count())
534
        # The next spills to the 2-len slot
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
535
        builder.add_node(*nodes[4])
536
        builder.add_node(*nodes[5])
537
        self.assertEqual(0, len(builder._nodes))
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
538
        self.assertIs(None, builder._nodes_by_key)
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
539
        self.assertEqual(2, len(builder._backing_indices))
540
        self.assertEqual(2, builder._backing_indices[0].key_count())
541
        self.assertEqual(4, builder._backing_indices[1].key_count())
542
        # Next spill combines
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
543
        builder.add_node(*nodes[6])
544
        builder.add_node(*nodes[7])
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
545
        self.assertEqual(3, len(builder._backing_indices))
546
        self.assertEqual(None, builder._backing_indices[0])
547
        self.assertEqual(None, builder._backing_indices[1])
548
        self.assertEqual(8, builder._backing_indices[2].key_count())
549
        # And so forth - counting up in binary.
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
550
        builder.add_node(*nodes[8])
551
        builder.add_node(*nodes[9])
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
552
        self.assertEqual(3, len(builder._backing_indices))
553
        self.assertEqual(2, builder._backing_indices[0].key_count())
554
        self.assertEqual(None, builder._backing_indices[1])
555
        self.assertEqual(8, builder._backing_indices[2].key_count())
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
556
        builder.add_node(*nodes[10])
557
        builder.add_node(*nodes[11])
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
558
        self.assertEqual(3, len(builder._backing_indices))
559
        self.assertEqual(None, builder._backing_indices[0])
560
        self.assertEqual(4, builder._backing_indices[1].key_count())
561
        self.assertEqual(8, builder._backing_indices[2].key_count())
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
562
        builder.add_node(*nodes[12])
563
        # Test that memory and disk are both used for query methods; and that
564
        # None is skipped over happily.
565
        self.assertEqual([(builder,) + node for node in sorted(nodes[:13])],
566
            list(builder.iter_all_entries()))
567
        # Two nodes - one memory one disk
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
568
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
569
            set(builder.iter_entries([nodes[12][0], nodes[11][0]])))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
570
        self.assertEqual(13, builder.key_count())
3644.2.5 by John Arbash Meinel
Restore the exact old tests, only assert that
571
        self.assertEqual(set([(builder,) + node for node in nodes[11:13]]),
572
            set(builder.iter_entries_prefix([nodes[12][0], nodes[11][0]])))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
573
        builder.add_node(*nodes[13])
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
574
        self.assertEqual(3, len(builder._backing_indices))
575
        self.assertEqual(2, builder._backing_indices[0].key_count())
576
        self.assertEqual(4, builder._backing_indices[1].key_count())
577
        self.assertEqual(8, builder._backing_indices[2].key_count())
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
578
        builder.add_node(*nodes[14])
579
        builder.add_node(*nodes[15])
4168.3.4 by John Arbash Meinel
Restore the ability to spill, but prepare a flag to disable it.
580
        self.assertEqual(4, len(builder._backing_indices))
581
        self.assertEqual(None, builder._backing_indices[0])
582
        self.assertEqual(None, builder._backing_indices[1])
583
        self.assertEqual(None, builder._backing_indices[2])
584
        self.assertEqual(16, builder._backing_indices[3].key_count())
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
585
        # Now finish, and check we got a correctly ordered tree
586
        transport = self.get_transport('')
587
        size = transport.put_file('index', builder.finish())
588
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
589
        nodes = list(index.iter_all_entries())
590
        self.assertEqual(sorted(nodes), nodes)
591
        self.assertEqual(16, len(nodes))
592
593
    def test_spill_index_duplicate_key_caught_on_finish(self):
594
        builder = btree_index.BTreeBuilder(key_elements=1, spill_at=2)
595
        nodes = [node[0:2] for node in self.make_nodes(16, 1, 0)]
596
        builder.add_node(*nodes[0])
597
        builder.add_node(*nodes[1])
598
        builder.add_node(*nodes[0])
599
        self.assertRaises(errors.BadIndexDuplicateKey, builder.finish)
600
601
602
class TestBTreeIndex(BTreeTestCase):
603
604
    def make_index(self, ref_lists=0, key_elements=1, nodes=[]):
605
        builder = btree_index.BTreeBuilder(reference_lists=ref_lists,
606
            key_elements=key_elements)
607
        for key, value, references in nodes:
608
            builder.add_node(key, value, references)
609
        stream = builder.finish()
610
        trans = get_transport('trace+' + self.get_url())
611
        size = trans.put_file('index', stream)
612
        return btree_index.BTreeGraphIndex(trans, 'index', size)
613
4744.2.6 by John Arbash Meinel
Start exposing an GraphIndex.clear_cache() member.
614
    def test_clear_cache(self):
615
        nodes = self.make_nodes(160, 2, 2)
616
        index = self.make_index(ref_lists=2, key_elements=2, nodes=nodes)
617
        self.assertEqual(1, len(list(index.iter_entries([nodes[30][0]]))))
618
        self.assertEqual([1, 4], index._row_lengths)
619
        self.assertIsNot(None, index._root_node)
4744.2.9 by John Arbash Meinel
Move the note and assertions.
620
        internal_node_pre_clear = index._internal_node_cache.keys()
4744.2.6 by John Arbash Meinel
Start exposing an GraphIndex.clear_cache() member.
621
        self.assertTrue(len(index._leaf_node_cache) > 0)
622
        index.clear_cache()
623
        # We don't touch _root_node or _internal_node_cache, both should be
624
        # small, and can save a round trip or two
625
        self.assertIsNot(None, index._root_node)
4744.2.9 by John Arbash Meinel
Move the note and assertions.
626
        # NOTE: We don't want to affect the _internal_node_cache, as we expect
627
        #       it will be small, and if we ever do touch this index again, it
628
        #       will save round-trips.  This assertion isn't very strong,
629
        #       becuase without a 3-level index, we don't have any internal
630
        #       nodes cached.
631
        self.assertEqual(internal_node_pre_clear,
632
                         index._internal_node_cache.keys())
4744.2.6 by John Arbash Meinel
Start exposing an GraphIndex.clear_cache() member.
633
        self.assertEqual(0, len(index._leaf_node_cache))
634
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
635
    def test_trivial_constructor(self):
636
        transport = get_transport('trace+' + self.get_url(''))
637
        index = btree_index.BTreeGraphIndex(transport, 'index', None)
638
        # Checks the page size at load, but that isn't logged yet.
639
        self.assertEqual([], transport._activity)
640
641
    def test_with_size_constructor(self):
642
        transport = get_transport('trace+' + self.get_url(''))
643
        index = btree_index.BTreeGraphIndex(transport, 'index', 1)
644
        # Checks the page size at load, but that isn't logged yet.
645
        self.assertEqual([], transport._activity)
646
647
    def test_empty_key_count_no_size(self):
648
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
649
        transport = get_transport('trace+' + self.get_url(''))
650
        transport.put_file('index', builder.finish())
651
        index = btree_index.BTreeGraphIndex(transport, 'index', None)
652
        del transport._activity[:]
653
        self.assertEqual([], transport._activity)
654
        self.assertEqual(0, index.key_count())
655
        # The entire index should have been requested (as we generally have the
656
        # size available, and doing many small readvs is inappropriate).
657
        # We can't tell how much was actually read here, but - check the code.
3824.1.2 by John Arbash Meinel
iter_all_entries() shouldn't need to re-read the page.
658
        self.assertEqual([('get', 'index')], transport._activity)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
659
660
    def test_empty_key_count(self):
661
        builder = btree_index.BTreeBuilder(key_elements=1, reference_lists=0)
662
        transport = get_transport('trace+' + self.get_url(''))
663
        size = transport.put_file('index', builder.finish())
664
        self.assertEqual(72, size)
665
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
666
        del transport._activity[:]
667
        self.assertEqual([], transport._activity)
668
        self.assertEqual(0, index.key_count())
669
        # The entire index should have been read, as 4K > size
670
        self.assertEqual([('readv', 'index', [(0, 72)], False, None)],
671
            transport._activity)
672
673
    def test_non_empty_key_count_2_2(self):
674
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
675
        nodes = self.make_nodes(35, 2, 2)
676
        for node in nodes:
677
            builder.add_node(*node)
678
        transport = get_transport('trace+' + self.get_url(''))
679
        size = transport.put_file('index', builder.finish())
680
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
681
        del transport._activity[:]
682
        self.assertEqual([], transport._activity)
683
        self.assertEqual(70, index.key_count())
684
        # The entire index should have been read, as it is one page long.
685
        self.assertEqual([('readv', 'index', [(0, size)], False, None)],
686
            transport._activity)
4771.3.1 by John Arbash Meinel
We don't have to pad 'short' records.
687
        self.assertEqual(1173, size)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
688
3824.1.1 by John Arbash Meinel
Fix _read_nodes() to only issue a single read if there is no known size.
689
    def test__read_nodes_no_size_one_page_reads_once(self):
690
        self.make_index(nodes=[(('key',), 'value', ())])
691
        trans = get_transport('trace+' + self.get_url())
692
        index = btree_index.BTreeGraphIndex(trans, 'index', None)
693
        del trans._activity[:]
694
        nodes = dict(index._read_nodes([0]))
695
        self.assertEqual([0], nodes.keys())
696
        node = nodes[0]
697
        self.assertEqual([('key',)], node.keys.keys())
698
        self.assertEqual([('get', 'index')], trans._activity)
699
700
    def test__read_nodes_no_size_multiple_pages(self):
701
        index = self.make_index(2, 2, nodes=self.make_nodes(160, 2, 2))
702
        index.key_count()
703
        num_pages = index._row_offsets[-1]
704
        # Reopen with a traced transport and no size
705
        trans = get_transport('trace+' + self.get_url())
706
        index = btree_index.BTreeGraphIndex(trans, 'index', None)
707
        del trans._activity[:]
708
        nodes = dict(index._read_nodes([0]))
709
        self.assertEqual(range(num_pages), nodes.keys())
710
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
711
    def test_2_levels_key_count_2_2(self):
712
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
713
        nodes = self.make_nodes(160, 2, 2)
714
        for node in nodes:
715
            builder.add_node(*node)
716
        transport = get_transport('trace+' + self.get_url(''))
717
        size = transport.put_file('index', builder.finish())
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
718
        self.assertEqual(17692, size)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
719
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
720
        del transport._activity[:]
721
        self.assertEqual([], transport._activity)
722
        self.assertEqual(320, index.key_count())
723
        # The entire index should not have been read.
724
        self.assertEqual([('readv', 'index', [(0, 4096)], False, None)],
725
            transport._activity)
726
727
    def test_validate_one_page(self):
728
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
729
        nodes = self.make_nodes(45, 2, 2)
730
        for node in nodes:
731
            builder.add_node(*node)
732
        transport = get_transport('trace+' + self.get_url(''))
733
        size = transport.put_file('index', builder.finish())
734
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
735
        del transport._activity[:]
736
        self.assertEqual([], transport._activity)
737
        index.validate()
738
        # The entire index should have been read linearly.
739
        self.assertEqual([('readv', 'index', [(0, size)], False, None)],
740
            transport._activity)
4771.3.1 by John Arbash Meinel
We don't have to pad 'short' records.
741
        self.assertEqual(1488, size)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
742
743
    def test_validate_two_pages(self):
744
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
745
        nodes = self.make_nodes(80, 2, 2)
746
        for node in nodes:
747
            builder.add_node(*node)
748
        transport = get_transport('trace+' + self.get_url(''))
749
        size = transport.put_file('index', builder.finish())
750
        # Root page, 2 leaf pages
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
751
        self.assertEqual(9339, size)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
752
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
753
        del transport._activity[:]
754
        self.assertEqual([], transport._activity)
755
        index.validate()
756
        # The entire index should have been read linearly.
757
        self.assertEqual([('readv', 'index', [(0, 4096)], False, None),
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
758
            ('readv', 'index', [(4096, 4096), (8192, 1147)], False, None)],
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
759
            transport._activity)
760
        # XXX: TODO: write some badly-ordered nodes, and some pointers-to-wrong
761
        # node and make validate find them.
762
763
    def test_eq_ne(self):
764
        # two indices are equal when constructed with the same parameters:
765
        transport1 = get_transport('trace+' + self.get_url(''))
766
        transport2 = get_transport(self.get_url(''))
767
        self.assertTrue(
768
            btree_index.BTreeGraphIndex(transport1, 'index', None) ==
769
            btree_index.BTreeGraphIndex(transport1, 'index', None))
770
        self.assertTrue(
771
            btree_index.BTreeGraphIndex(transport1, 'index', 20) ==
772
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
773
        self.assertFalse(
774
            btree_index.BTreeGraphIndex(transport1, 'index', 20) ==
775
            btree_index.BTreeGraphIndex(transport2, 'index', 20))
776
        self.assertFalse(
777
            btree_index.BTreeGraphIndex(transport1, 'inde1', 20) ==
778
            btree_index.BTreeGraphIndex(transport1, 'inde2', 20))
779
        self.assertFalse(
780
            btree_index.BTreeGraphIndex(transport1, 'index', 10) ==
781
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
782
        self.assertFalse(
783
            btree_index.BTreeGraphIndex(transport1, 'index', None) !=
784
            btree_index.BTreeGraphIndex(transport1, 'index', None))
785
        self.assertFalse(
786
            btree_index.BTreeGraphIndex(transport1, 'index', 20) !=
787
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
788
        self.assertTrue(
789
            btree_index.BTreeGraphIndex(transport1, 'index', 20) !=
790
            btree_index.BTreeGraphIndex(transport2, 'index', 20))
791
        self.assertTrue(
792
            btree_index.BTreeGraphIndex(transport1, 'inde1', 20) !=
793
            btree_index.BTreeGraphIndex(transport1, 'inde2', 20))
794
        self.assertTrue(
795
            btree_index.BTreeGraphIndex(transport1, 'index', 10) !=
796
            btree_index.BTreeGraphIndex(transport1, 'index', 20))
797
3824.1.2 by John Arbash Meinel
iter_all_entries() shouldn't need to re-read the page.
798
    def test_iter_all_only_root_no_size(self):
799
        self.make_index(nodes=[(('key',), 'value', ())])
800
        trans = get_transport('trace+' + self.get_url(''))
801
        index = btree_index.BTreeGraphIndex(trans, 'index', None)
802
        del trans._activity[:]
803
        self.assertEqual([(('key',), 'value')],
804
                         [x[1:] for x in index.iter_all_entries()])
805
        self.assertEqual([('get', 'index')], trans._activity)
806
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
807
    def test_iter_all_entries_reads(self):
808
        # iterating all entries reads the header, then does a linear
809
        # read.
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
810
        self.shrink_page_size()
3641.5.11 by John Arbash Meinel
Some small test cleanup
811
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
812
        # 20k nodes is enough to create a two internal nodes on the second
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
813
        # level, with a 2K page size
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
814
        nodes = self.make_nodes(10000, 2, 2)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
815
        for node in nodes:
816
            builder.add_node(*node)
817
        transport = get_transport('trace+' + self.get_url(''))
818
        size = transport.put_file('index', builder.finish())
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
819
        self.assertEqual(1303220, size, 'number of expected bytes in the'
820
                                        ' output changed')
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
821
        page_size = btree_index._PAGE_SIZE
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
822
        del builder
823
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
824
        del transport._activity[:]
825
        self.assertEqual([], transport._activity)
3641.5.8 by John Arbash Meinel
Fix up the test suite, now that we pack more efficiently.
826
        found_nodes = self.time(list, index.iter_all_entries())
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
827
        bare_nodes = []
828
        for node in found_nodes:
829
            self.assertTrue(node[0] is index)
830
            bare_nodes.append(node[1:])
831
        self.assertEqual(3, len(index._row_lengths),
832
            "Not enough rows: %r" % index._row_lengths)
833
        # Should be as long as the nodes we supplied
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
834
        self.assertEqual(20000, len(found_nodes))
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
835
        # Should have the same content
836
        self.assertEqual(set(nodes), set(bare_nodes))
837
        # Should have done linear scan IO up the index, ignoring
838
        # the internal nodes:
839
        # The entire index should have been read
840
        total_pages = sum(index._row_lengths)
841
        self.assertEqual(total_pages, index._row_offsets[-1])
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
842
        self.assertEqual(1303220, size)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
843
        # The start of the leaves
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
844
        first_byte = index._row_offsets[-2] * page_size
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
845
        readv_request = []
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
846
        for offset in range(first_byte, size, page_size):
847
            readv_request.append((offset, page_size))
3641.3.6 by John Arbash Meinel
Dropping the number of nodes to 100k from 200k
848
        # The last page is truncated
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
849
        readv_request[-1] = (readv_request[-1][0], 1303220 % page_size)
3641.5.9 by John Arbash Meinel
Shrink the page size so that the three_level and iter_all
850
        expected = [('readv', 'index', [(0, page_size)], False, None),
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
851
             ('readv',  'index', readv_request, False, None)]
852
        if expected != transport._activity:
853
            self.assertEqualDiff(pprint.pformat(expected),
854
                                 pprint.pformat(transport._activity))
855
856
    def _test_iter_entries_references_resolved(self):
857
        index = self.make_index(1, nodes=[
858
            (('name', ), 'data', ([('ref', ), ('ref', )], )),
859
            (('ref', ), 'refdata', ([], ))])
860
        self.assertEqual(set([(index, ('name', ), 'data', ((('ref',),('ref',)),)),
861
            (index, ('ref', ), 'refdata', ((), ))]),
862
            set(index.iter_entries([('name',), ('ref',)])))
863
864
    def test_iter_entries_references_2_refs_resolved(self):
865
        # iterating some entries reads just the pages needed. For now, to
866
        # get it working and start measuring, only 4K pages are read.
867
        builder = btree_index.BTreeBuilder(key_elements=2, reference_lists=2)
868
        # 80 nodes is enough to create a two-level index.
869
        nodes = self.make_nodes(160, 2, 2)
870
        for node in nodes:
871
            builder.add_node(*node)
872
        transport = get_transport('trace+' + self.get_url(''))
873
        size = transport.put_file('index', builder.finish())
874
        del builder
875
        index = btree_index.BTreeGraphIndex(transport, 'index', size)
876
        del transport._activity[:]
877
        self.assertEqual([], transport._activity)
878
        # search for one key
879
        found_nodes = list(index.iter_entries([nodes[30][0]]))
880
        bare_nodes = []
881
        for node in found_nodes:
882
            self.assertTrue(node[0] is index)
883
            bare_nodes.append(node[1:])
884
        # Should be as long as the nodes we supplied
885
        self.assertEqual(1, len(found_nodes))
886
        # Should have the same content
887
        self.assertEqual(nodes[30], bare_nodes[0])
888
        # Should have read the root node, then one leaf page:
889
        self.assertEqual([('readv', 'index', [(0, 4096)], False, None),
3641.5.17 by John Arbash Meinel
Fix up the test suite now that things don't pack as well.
890
             ('readv',  'index', [(8192, 4096), ], False, None)],
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
891
            transport._activity)
892
893
    def test_iter_key_prefix_1_element_key_None(self):
894
        index = self.make_index()
895
        self.assertRaises(errors.BadIndexKey, list,
896
            index.iter_entries_prefix([(None, )]))
897
898
    def test_iter_key_prefix_wrong_length(self):
899
        index = self.make_index()
900
        self.assertRaises(errors.BadIndexKey, list,
901
            index.iter_entries_prefix([('foo', None)]))
902
        index = self.make_index(key_elements=2)
903
        self.assertRaises(errors.BadIndexKey, list,
904
            index.iter_entries_prefix([('foo', )]))
905
        self.assertRaises(errors.BadIndexKey, list,
906
            index.iter_entries_prefix([('foo', None, None)]))
907
908
    def test_iter_key_prefix_1_key_element_no_refs(self):
909
        index = self.make_index( nodes=[
910
            (('name', ), 'data', ()),
911
            (('ref', ), 'refdata', ())])
912
        self.assertEqual(set([(index, ('name', ), 'data'),
913
            (index, ('ref', ), 'refdata')]),
914
            set(index.iter_entries_prefix([('name', ), ('ref', )])))
915
916
    def test_iter_key_prefix_1_key_element_refs(self):
917
        index = self.make_index(1, nodes=[
918
            (('name', ), 'data', ([('ref', )], )),
919
            (('ref', ), 'refdata', ([], ))])
920
        self.assertEqual(set([(index, ('name', ), 'data', ((('ref',),),)),
921
            (index, ('ref', ), 'refdata', ((), ))]),
922
            set(index.iter_entries_prefix([('name', ), ('ref', )])))
923
924
    def test_iter_key_prefix_2_key_element_no_refs(self):
925
        index = self.make_index(key_elements=2, nodes=[
926
            (('name', 'fin1'), 'data', ()),
927
            (('name', 'fin2'), 'beta', ()),
928
            (('ref', 'erence'), 'refdata', ())])
929
        self.assertEqual(set([(index, ('name', 'fin1'), 'data'),
930
            (index, ('ref', 'erence'), 'refdata')]),
931
            set(index.iter_entries_prefix([('name', 'fin1'), ('ref', 'erence')])))
932
        self.assertEqual(set([(index, ('name', 'fin1'), 'data'),
933
            (index, ('name', 'fin2'), 'beta')]),
934
            set(index.iter_entries_prefix([('name', None)])))
935
936
    def test_iter_key_prefix_2_key_element_refs(self):
937
        index = self.make_index(1, key_elements=2, nodes=[
938
            (('name', 'fin1'), 'data', ([('ref', 'erence')], )),
939
            (('name', 'fin2'), 'beta', ([], )),
940
            (('ref', 'erence'), 'refdata', ([], ))])
941
        self.assertEqual(set([(index, ('name', 'fin1'), 'data', ((('ref', 'erence'),),)),
942
            (index, ('ref', 'erence'), 'refdata', ((), ))]),
943
            set(index.iter_entries_prefix([('name', 'fin1'), ('ref', 'erence')])))
944
        self.assertEqual(set([(index, ('name', 'fin1'), 'data', ((('ref', 'erence'),),)),
945
            (index, ('name', 'fin2'), 'beta', ((), ))]),
946
            set(index.iter_entries_prefix([('name', None)])))
947
4011.5.5 by Andrew Bennetts
Fix typo in comment.
948
    # XXX: external_references tests are duplicated in test_index.  We
4011.5.3 by Andrew Bennetts
Implement and test external_references on GraphIndex and BTreeGraphIndex.
949
    # probably should have per_graph_index tests...
950
    def test_external_references_no_refs(self):
951
        index = self.make_index(ref_lists=0, nodes=[])
952
        self.assertRaises(ValueError, index.external_references, 0)
953
954
    def test_external_references_no_results(self):
955
        index = self.make_index(ref_lists=1, nodes=[
956
            (('key',), 'value', ([],))])
957
        self.assertEqual(set(), index.external_references(0))
958
959
    def test_external_references_missing_ref(self):
960
        missing_key = ('missing',)
961
        index = self.make_index(ref_lists=1, nodes=[
962
            (('key',), 'value', ([missing_key],))])
963
        self.assertEqual(set([missing_key]), index.external_references(0))
964
965
    def test_external_references_multiple_ref_lists(self):
966
        missing_key = ('missing',)
967
        index = self.make_index(ref_lists=2, nodes=[
968
            (('key',), 'value', ([], [missing_key]))])
969
        self.assertEqual(set([]), index.external_references(0))
970
        self.assertEqual(set([missing_key]), index.external_references(1))
971
972
    def test_external_references_two_records(self):
973
        index = self.make_index(ref_lists=1, nodes=[
974
            (('key-1',), 'value', ([('key-2',)],)),
975
            (('key-2',), 'value', ([],)),
976
            ])
977
        self.assertEqual(set([]), index.external_references(0))
978
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
979
    def test__find_ancestors_one_page(self):
4593.4.5 by John Arbash Meinel
Start adding some tests.
980
        key1 = ('key-1',)
981
        key2 = ('key-2',)
982
        index = self.make_index(ref_lists=1, key_elements=1, nodes=[
983
            (key1, 'value', ([key2],)),
984
            (key2, 'value', ([],)),
985
            ])
986
        parent_map = {}
987
        missing_keys = set()
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
988
        search_keys = index._find_ancestors([key1], 0, parent_map, missing_keys)
4593.4.5 by John Arbash Meinel
Start adding some tests.
989
        self.assertEqual({key1: (key2,), key2: ()}, parent_map)
990
        self.assertEqual(set(), missing_keys)
4593.4.7 by John Arbash Meinel
Basic implementation of a conforming interface for GraphIndex.
991
        self.assertEqual(set(), search_keys)
4593.4.5 by John Arbash Meinel
Start adding some tests.
992
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
993
    def test__find_ancestors_one_page_w_missing(self):
4593.4.6 by John Arbash Meinel
Add a test that we can walk some of the keys on a given page
994
        key1 = ('key-1',)
995
        key2 = ('key-2',)
996
        key3 = ('key-3',)
997
        index = self.make_index(ref_lists=1, key_elements=1, nodes=[
998
            (key1, 'value', ([key2],)),
999
            (key2, 'value', ([],)),
1000
            ])
1001
        parent_map = {}
1002
        missing_keys = set()
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
1003
        search_keys = index._find_ancestors([key2, key3], 0, parent_map,
1004
                                            missing_keys)
4593.4.6 by John Arbash Meinel
Add a test that we can walk some of the keys on a given page
1005
        self.assertEqual({key2: ()}, parent_map)
1006
        # we know that key3 is missing because we read the page that it would
1007
        # otherwise be on
1008
        self.assertEqual(set([key3]), missing_keys)
1009
        self.assertEqual(set(), search_keys)
1010
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
1011
    def test__find_ancestors_one_parent_missing(self):
4593.4.6 by John Arbash Meinel
Add a test that we can walk some of the keys on a given page
1012
        key1 = ('key-1',)
1013
        key2 = ('key-2',)
1014
        key3 = ('key-3',)
1015
        index = self.make_index(ref_lists=1, key_elements=1, nodes=[
1016
            (key1, 'value', ([key2],)),
1017
            (key2, 'value', ([key3],)),
1018
            ])
1019
        parent_map = {}
1020
        missing_keys = set()
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
1021
        search_keys = index._find_ancestors([key1], 0, parent_map,
1022
                                            missing_keys)
4593.4.6 by John Arbash Meinel
Add a test that we can walk some of the keys on a given page
1023
        self.assertEqual({key1: (key2,), key2: (key3,)}, parent_map)
1024
        self.assertEqual(set(), missing_keys)
1025
        # all we know is that key3 wasn't present on the page we were reading
1026
        # but if you look, the last key is key2 which comes before key3, so we
1027
        # don't know whether key3 would land on this page or not.
1028
        self.assertEqual(set([key3]), search_keys)
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
1029
        search_keys = index._find_ancestors(search_keys, 0, parent_map,
1030
                                            missing_keys)
4593.4.6 by John Arbash Meinel
Add a test that we can walk some of the keys on a given page
1031
        # passing it back in, we are sure it is 'missing'
1032
        self.assertEqual({key1: (key2,), key2: (key3,)}, parent_map)
1033
        self.assertEqual(set([key3]), missing_keys)
1034
        self.assertEqual(set([]), search_keys)
1035
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
1036
    def test__find_ancestors_dont_search_known(self):
4593.4.7 by John Arbash Meinel
Basic implementation of a conforming interface for GraphIndex.
1037
        key1 = ('key-1',)
1038
        key2 = ('key-2',)
1039
        key3 = ('key-3',)
1040
        index = self.make_index(ref_lists=1, key_elements=1, nodes=[
1041
            (key1, 'value', ([key2],)),
1042
            (key2, 'value', ([key3],)),
1043
            (key3, 'value', ([],)),
1044
            ])
1045
        # We already know about key2, so we won't try to search for key3
1046
        parent_map = {key2: (key3,)}
1047
        missing_keys = set()
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
1048
        search_keys = index._find_ancestors([key1], 0, parent_map,
1049
                                            missing_keys)
4593.4.7 by John Arbash Meinel
Basic implementation of a conforming interface for GraphIndex.
1050
        self.assertEqual({key1: (key2,), key2: (key3,)}, parent_map)
1051
        self.assertEqual(set(), missing_keys)
1052
        self.assertEqual(set(), search_keys)
1053
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
1054
    def test__find_ancestors_multiple_pages(self):
4593.4.6 by John Arbash Meinel
Add a test that we can walk some of the keys on a given page
1055
        # We need to use enough keys that we actually cause a split
1056
        start_time = 1249671539
1057
        email = "joebob@example.com"
1058
        nodes = []
1059
        ref_lists = ((),)
1060
        rev_keys = []
1061
        for i in xrange(400):
1062
            rev_id = '%s-%s-%s' % (email,
1063
                                   osutils.compact_date(start_time + i),
1064
                                   osutils.rand_chars(16))
1065
            rev_key = (rev_id,)
1066
            nodes.append((rev_key, 'value', ref_lists))
1067
            # We have a ref 'list' of length 1, with a list of parents, with 1
1068
            # parent which is a key
1069
            ref_lists = ((rev_key,),)
1070
            rev_keys.append(rev_key)
1071
        index = self.make_index(ref_lists=1, key_elements=1, nodes=nodes)
1072
        self.assertEqual(400, index.key_count())
1073
        self.assertEqual(3, len(index._row_offsets))
1074
        nodes = dict(index._read_nodes([1, 2]))
1075
        l1 = nodes[1]
1076
        l2 = nodes[2]
1077
        min_l2_key = l2.min_key
1078
        max_l1_key = l1.max_key
1079
        self.assertTrue(max_l1_key < min_l2_key)
1080
        parents_min_l2_key = l2.keys[min_l2_key][1][0]
1081
        self.assertEqual((l1.max_key,), parents_min_l2_key)
1082
        # Now, whatever key we select that would fall on the second page,
1083
        # should give us all the parents until the page break
1084
        key_idx = rev_keys.index(min_l2_key)
1085
        next_key = rev_keys[key_idx+1]
1086
        # So now when we get the parent map, we should get the key we are
1087
        # looking for, min_l2_key, and then a reference to go look for the
1088
        # parent of that key
1089
        parent_map = {}
1090
        missing_keys = set()
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
1091
        search_keys = index._find_ancestors([next_key], 0, parent_map,
1092
                                            missing_keys)
4593.4.6 by John Arbash Meinel
Add a test that we can walk some of the keys on a given page
1093
        self.assertEqual([min_l2_key, next_key], sorted(parent_map))
1094
        self.assertEqual(set(), missing_keys)
1095
        self.assertEqual(set([max_l1_key]), search_keys)
1096
        parent_map = {}
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
1097
        search_keys = index._find_ancestors([max_l1_key], 0, parent_map,
1098
                                            missing_keys)
4593.4.6 by John Arbash Meinel
Add a test that we can walk some of the keys on a given page
1099
        self.assertEqual(sorted(l1.keys), sorted(parent_map))
1100
        self.assertEqual(set(), missing_keys)
1101
        self.assertEqual(set(), search_keys)
1102
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
1103
    def test__find_ancestors_empty_index(self):
4593.4.11 by John Arbash Meinel
Snapshot the work in progress.
1104
        index = self.make_index(ref_lists=1, key_elements=1, nodes=[])
1105
        parent_map = {}
1106
        missing_keys = set()
4593.4.12 by John Arbash Meinel
Name the specific index api _find_ancestors, and the public CombinedGraphIndex api find_ancestry()
1107
        search_keys = index._find_ancestors([('one',), ('two',)], 0, parent_map,
1108
                                            missing_keys)
4593.4.11 by John Arbash Meinel
Snapshot the work in progress.
1109
        self.assertEqual(set(), search_keys)
1110
        self.assertEqual({}, parent_map)
1111
        self.assertEqual(set([('one',), ('two',)]), missing_keys)
1112
4634.71.1 by John Arbash Meinel
Work around bug #402623 by allowing BTreeGraphIndex(...,unlimited_cache=True).
1113
    def test_supports_unlimited_cache(self):
1114
        builder = btree_index.BTreeBuilder(reference_lists=0, key_elements=1)
4634.71.3 by John Arbash Meinel
Add some comments to test_supports_unlimited_cache, as mentioned by Vincent.
1115
        # We need enough nodes to cause a page split (so we have both an
1116
        # internal node and a couple leaf nodes. 500 seems to be enough.)
4634.71.2 by John Arbash Meinel
If we are going to sometimes use a dict, we have to conform to just the dict interface.
1117
        nodes = self.make_nodes(500, 1, 0)
1118
        for node in nodes:
1119
            builder.add_node(*node)
4634.71.1 by John Arbash Meinel
Work around bug #402623 by allowing BTreeGraphIndex(...,unlimited_cache=True).
1120
        stream = builder.finish()
1121
        trans = get_transport(self.get_url())
1122
        size = trans.put_file('index', stream)
1123
        index = btree_index.BTreeGraphIndex(trans, 'index', size)
4634.71.3 by John Arbash Meinel
Add some comments to test_supports_unlimited_cache, as mentioned by Vincent.
1124
        self.assertEqual(500, index.key_count())
1125
        # We have an internal node
1126
        self.assertEqual(2, len(index._row_lengths))
1127
        # We have at least 2 leaf nodes
1128
        self.assertTrue(index._row_lengths[-1] >= 2)
4634.71.1 by John Arbash Meinel
Work around bug #402623 by allowing BTreeGraphIndex(...,unlimited_cache=True).
1129
        self.assertIsInstance(index._leaf_node_cache, lru_cache.LRUCache)
1130
        self.assertEqual(btree_index._NODE_CACHE_SIZE,
1131
                         index._leaf_node_cache._max_cache)
1132
        self.assertIsInstance(index._internal_node_cache, fifo_cache.FIFOCache)
1133
        self.assertEqual(100, index._internal_node_cache._max_cache)
1134
        # No change if unlimited_cache=False is passed
1135
        index = btree_index.BTreeGraphIndex(trans, 'index', size,
1136
                                            unlimited_cache=False)
1137
        self.assertIsInstance(index._leaf_node_cache, lru_cache.LRUCache)
1138
        self.assertEqual(btree_index._NODE_CACHE_SIZE,
1139
                         index._leaf_node_cache._max_cache)
1140
        self.assertIsInstance(index._internal_node_cache, fifo_cache.FIFOCache)
1141
        self.assertEqual(100, index._internal_node_cache._max_cache)
1142
        index = btree_index.BTreeGraphIndex(trans, 'index', size,
1143
                                            unlimited_cache=True)
1144
        self.assertIsInstance(index._leaf_node_cache, dict)
1145
        self.assertIs(type(index._internal_node_cache), dict)
4634.71.3 by John Arbash Meinel
Add some comments to test_supports_unlimited_cache, as mentioned by Vincent.
1146
        # Exercise the lookup code
1147
        entries = set(index.iter_entries([n[0] for n in nodes]))
1148
        self.assertEqual(500, len(entries))
4634.71.1 by John Arbash Meinel
Work around bug #402623 by allowing BTreeGraphIndex(...,unlimited_cache=True).
1149
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
1150
1151
class TestBTreeNodes(BTreeTestCase):
1152
1153
    def setUp(self):
1154
        BTreeTestCase.setUp(self)
4985.1.5 by Vincent Ladeuil
Deploying the new overrideAttr facility further reduces the complexity
1155
        self.overrideAttr(btree_index, '_btree_serializer', self.parse_btree)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
1156
1157
    def test_LeafNode_1_0(self):
1158
        node_bytes = ("type=leaf\n"
1159
            "0000000000000000000000000000000000000000\x00\x00value:0\n"
1160
            "1111111111111111111111111111111111111111\x00\x00value:1\n"
1161
            "2222222222222222222222222222222222222222\x00\x00value:2\n"
1162
            "3333333333333333333333333333333333333333\x00\x00value:3\n"
1163
            "4444444444444444444444444444444444444444\x00\x00value:4\n")
1164
        node = btree_index._LeafNode(node_bytes, 1, 0)
1165
        # We do direct access, or don't care about order, to leaf nodes most of
1166
        # the time, so a dict is useful:
1167
        self.assertEqual({
1168
            ("0000000000000000000000000000000000000000",): ("value:0", ()),
1169
            ("1111111111111111111111111111111111111111",): ("value:1", ()),
1170
            ("2222222222222222222222222222222222222222",): ("value:2", ()),
1171
            ("3333333333333333333333333333333333333333",): ("value:3", ()),
1172
            ("4444444444444444444444444444444444444444",): ("value:4", ()),
1173
            }, node.keys)
1174
1175
    def test_LeafNode_2_2(self):
1176
        node_bytes = ("type=leaf\n"
1177
            "00\x0000\x00\t00\x00ref00\x00value:0\n"
1178
            "00\x0011\x0000\x00ref00\t00\x00ref00\r01\x00ref01\x00value:1\n"
1179
            "11\x0033\x0011\x00ref22\t11\x00ref22\r11\x00ref22\x00value:3\n"
1180
            "11\x0044\x00\t11\x00ref00\x00value:4\n"
1181
            ""
1182
            )
1183
        node = btree_index._LeafNode(node_bytes, 2, 2)
1184
        # We do direct access, or don't care about order, to leaf nodes most of
1185
        # the time, so a dict is useful:
1186
        self.assertEqual({
1187
            ('00', '00'): ('value:0', ((), (('00', 'ref00'),))),
1188
            ('00', '11'): ('value:1',
1189
                ((('00', 'ref00'),), (('00', 'ref00'), ('01', 'ref01')))),
1190
            ('11', '33'): ('value:3',
1191
                ((('11', 'ref22'),), (('11', 'ref22'), ('11', 'ref22')))),
1192
            ('11', '44'): ('value:4', ((), (('11', 'ref00'),)))
1193
            }, node.keys)
1194
1195
    def test_InternalNode_1(self):
1196
        node_bytes = ("type=internal\n"
1197
            "offset=1\n"
1198
            "0000000000000000000000000000000000000000\n"
1199
            "1111111111111111111111111111111111111111\n"
1200
            "2222222222222222222222222222222222222222\n"
1201
            "3333333333333333333333333333333333333333\n"
1202
            "4444444444444444444444444444444444444444\n"
1203
            )
1204
        node = btree_index._InternalNode(node_bytes)
1205
        # We want to bisect to find the right children from this node, so a
1206
        # vector is most useful.
1207
        self.assertEqual([
1208
            ("0000000000000000000000000000000000000000",),
1209
            ("1111111111111111111111111111111111111111",),
1210
            ("2222222222222222222222222222222222222222",),
1211
            ("3333333333333333333333333333333333333333",),
1212
            ("4444444444444444444444444444444444444444",),
1213
            ], node.keys)
1214
        self.assertEqual(1, node.offset)
1215
1216
    def test_LeafNode_2_2(self):
1217
        node_bytes = ("type=leaf\n"
1218
            "00\x0000\x00\t00\x00ref00\x00value:0\n"
1219
            "00\x0011\x0000\x00ref00\t00\x00ref00\r01\x00ref01\x00value:1\n"
1220
            "11\x0033\x0011\x00ref22\t11\x00ref22\r11\x00ref22\x00value:3\n"
1221
            "11\x0044\x00\t11\x00ref00\x00value:4\n"
1222
            ""
1223
            )
1224
        node = btree_index._LeafNode(node_bytes, 2, 2)
1225
        # We do direct access, or don't care about order, to leaf nodes most of
1226
        # the time, so a dict is useful:
1227
        self.assertEqual({
1228
            ('00', '00'): ('value:0', ((), (('00', 'ref00'),))),
1229
            ('00', '11'): ('value:1',
1230
                ((('00', 'ref00'),), (('00', 'ref00'), ('01', 'ref01')))),
1231
            ('11', '33'): ('value:3',
1232
                ((('11', 'ref22'),), (('11', 'ref22'), ('11', 'ref22')))),
1233
            ('11', '44'): ('value:4', ((), (('11', 'ref00'),)))
1234
            }, node.keys)
1235
3641.3.19 by John Arbash Meinel
The flatten code now handles the no-ref-list case.
1236
    def assertFlattened(self, expected, key, value, refs):
3641.3.18 by John Arbash Meinel
Start working on a compiled function for transforming
1237
        flat_key, flat_line = self.parse_btree._flatten_node(
3641.3.19 by John Arbash Meinel
The flatten code now handles the no-ref-list case.
1238
            (None, key, value, refs), bool(refs))
3641.3.18 by John Arbash Meinel
Start working on a compiled function for transforming
1239
        self.assertEqual('\x00'.join(key), flat_key)
1240
        self.assertEqual(expected, flat_line)
1241
3641.3.19 by John Arbash Meinel
The flatten code now handles the no-ref-list case.
1242
    def test__flatten_node(self):
1243
        self.assertFlattened('key\0\0value\n', ('key',), 'value', [])
1244
        self.assertFlattened('key\0tuple\0\0value str\n',
1245
                             ('key', 'tuple'), 'value str', [])
1246
        self.assertFlattened('key\0tuple\0triple\0\0value str\n',
1247
                             ('key', 'tuple', 'triple'), 'value str', [])
1248
        self.assertFlattened('k\0t\0s\0ref\0value str\n',
1249
                             ('k', 't', 's'), 'value str', [[('ref',)]])
1250
        self.assertFlattened('key\0tuple\0ref\0key\0value str\n',
1251
                             ('key', 'tuple'), 'value str', [[('ref', 'key')]])
1252
        self.assertFlattened("00\x0000\x00\t00\x00ref00\x00value:0\n",
1253
            ('00', '00'), 'value:0', ((), (('00', 'ref00'),)))
1254
        self.assertFlattened(
1255
            "00\x0011\x0000\x00ref00\t00\x00ref00\r01\x00ref01\x00value:1\n",
1256
            ('00', '11'), 'value:1',
1257
                ((('00', 'ref00'),), (('00', 'ref00'), ('01', 'ref01'))))
1258
        self.assertFlattened(
1259
            "11\x0033\x0011\x00ref22\t11\x00ref22\r11\x00ref22\x00value:3\n",
1260
            ('11', '33'), 'value:3',
1261
                ((('11', 'ref22'),), (('11', 'ref22'), ('11', 'ref22'))))
1262
        self.assertFlattened(
1263
            "11\x0044\x00\t11\x00ref00\x00value:4\n",
1264
            ('11', '44'), 'value:4', ((), (('11', 'ref00'),)))
3641.3.18 by John Arbash Meinel
Start working on a compiled function for transforming
1265
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
1266
1267
class TestCompiledBtree(tests.TestCase):
1268
1269
    def test_exists(self):
1270
        # This is just to let the user know if they don't have the feature
1271
        # available
4913.2.20 by John Arbash Meinel
Change all of the compiled_foo to compiled_foo_feature
1272
        self.requireFeature(compiled_btreeparser_feature)
3641.3.1 by John Arbash Meinel
Bring in the btree_index and chunk_writer code and their tests.
1273
1274
1275
class TestMultiBisectRight(tests.TestCase):
1276
1277
    def assertMultiBisectRight(self, offsets, search_keys, fixed_keys):
1278
        self.assertEqual(offsets,
1279
                         btree_index.BTreeGraphIndex._multi_bisect_right(
1280
                            search_keys, fixed_keys))
1281
1282
    def test_after(self):
1283
        self.assertMultiBisectRight([(1, ['b'])], ['b'], ['a'])
1284
        self.assertMultiBisectRight([(3, ['e', 'f', 'g'])],
1285
                                    ['e', 'f', 'g'], ['a', 'b', 'c'])
1286
1287
    def test_before(self):
1288
        self.assertMultiBisectRight([(0, ['a'])], ['a'], ['b'])
1289
        self.assertMultiBisectRight([(0, ['a', 'b', 'c', 'd'])],
1290
                                    ['a', 'b', 'c', 'd'], ['e', 'f', 'g'])
1291
1292
    def test_exact(self):
1293
        self.assertMultiBisectRight([(1, ['a'])], ['a'], ['a'])
1294
        self.assertMultiBisectRight([(1, ['a']), (2, ['b'])], ['a', 'b'], ['a', 'b'])
1295
        self.assertMultiBisectRight([(1, ['a']), (3, ['c'])],
1296
                                    ['a', 'c'], ['a', 'b', 'c'])
1297
1298
    def test_inbetween(self):
1299
        self.assertMultiBisectRight([(1, ['b'])], ['b'], ['a', 'c'])
1300
        self.assertMultiBisectRight([(1, ['b', 'c', 'd']), (2, ['f', 'g'])],
1301
                                    ['b', 'c', 'd', 'f', 'g'], ['a', 'e', 'h'])
1302
1303
    def test_mixed(self):
1304
        self.assertMultiBisectRight([(0, ['a', 'b']), (2, ['d', 'e']),
1305
                                     (4, ['g', 'h'])],
1306
                                    ['a', 'b', 'd', 'e', 'g', 'h'],
1307
                                    ['c', 'd', 'f', 'g'])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1308
1309
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1310
class TestExpandOffsets(tests.TestCase):
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1311
1312
    def make_index(self, size, recommended_pages=None):
1313
        """Make an index with a generic size.
1314
1315
        This doesn't actually create anything on disk, it just primes a
1316
        BTreeGraphIndex with the recommended information.
1317
        """
1318
        index = btree_index.BTreeGraphIndex(get_transport('memory:///'),
1319
                                            'test-index', size=size)
1320
        if recommended_pages is not None:
1321
            index._recommended_pages = recommended_pages
1322
        return index
1323
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1324
    def set_cached_offsets(self, index, cached_offsets):
1325
        """Monkeypatch to give a canned answer for _get_offsets_for...()."""
1326
        def _get_offsets_to_cached_pages():
1327
            cached = set(cached_offsets)
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1328
            return cached
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1329
        index._get_offsets_to_cached_pages = _get_offsets_to_cached_pages
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1330
1331
    def prepare_index(self, index, node_ref_lists, key_length, key_count,
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1332
                      row_lengths, cached_offsets):
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1333
        """Setup the BTreeGraphIndex with some pre-canned information."""
1334
        index.node_ref_lists = node_ref_lists
1335
        index._key_length = key_length
1336
        index._key_count = key_count
1337
        index._row_lengths = row_lengths
1338
        index._compute_row_offsets()
1339
        index._root_node = btree_index._InternalNode('internal\noffset=0\n')
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1340
        self.set_cached_offsets(index, cached_offsets)
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1341
1342
    def make_100_node_index(self):
1343
        index = self.make_index(4096*100, 6)
3763.8.14 by John Arbash Meinel
Add in a shortcut when we haven't cached much yet.
1344
        # Consider we've already made a single request at the middle
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1345
        self.prepare_index(index, node_ref_lists=0, key_length=1,
1346
                           key_count=1000, row_lengths=[1, 99],
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1347
                           cached_offsets=[0, 50])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1348
        return index
1349
3763.8.9 by John Arbash Meinel
Finish up the algorithm to stay within a given layer.
1350
    def make_1000_node_index(self):
1351
        index = self.make_index(4096*1000, 6)
3763.8.14 by John Arbash Meinel
Add in a shortcut when we haven't cached much yet.
1352
        # Pretend we've already made a single request in the middle
3763.8.9 by John Arbash Meinel
Finish up the algorithm to stay within a given layer.
1353
        self.prepare_index(index, node_ref_lists=0, key_length=1,
1354
                           key_count=90000, row_lengths=[1, 9, 990],
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1355
                           cached_offsets=[0, 5, 500])
3763.8.9 by John Arbash Meinel
Finish up the algorithm to stay within a given layer.
1356
        return index
1357
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1358
    def assertNumPages(self, expected_pages, index, size):
1359
        index._size = size
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1360
        self.assertEqual(expected_pages, index._compute_total_pages_in_index())
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1361
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1362
    def assertExpandOffsets(self, expected, index, offsets):
1363
        self.assertEqual(expected, index._expand_offsets(offsets),
1364
                         'We did not get the expected value after expanding'
1365
                         ' %s' % (offsets,))
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1366
1367
    def test_default_recommended_pages(self):
1368
        index = self.make_index(None)
1369
        # local transport recommends 4096 byte reads, which is 1 page
1370
        self.assertEqual(1, index._recommended_pages)
1371
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1372
    def test__compute_total_pages_in_index(self):
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1373
        index = self.make_index(None)
1374
        self.assertNumPages(1, index, 1024)
1375
        self.assertNumPages(1, index, 4095)
1376
        self.assertNumPages(1, index, 4096)
1377
        self.assertNumPages(2, index, 4097)
1378
        self.assertNumPages(2, index, 8192)
1379
        self.assertNumPages(76, index, 4096*75 + 10)
1380
3763.8.9 by John Arbash Meinel
Finish up the algorithm to stay within a given layer.
1381
    def test__find_layer_start_and_stop(self):
1382
        index = self.make_1000_node_index()
1383
        self.assertEqual((0, 1), index._find_layer_first_and_end(0))
1384
        self.assertEqual((1, 10), index._find_layer_first_and_end(1))
1385
        self.assertEqual((1, 10), index._find_layer_first_and_end(9))
1386
        self.assertEqual((10, 1000), index._find_layer_first_and_end(10))
1387
        self.assertEqual((10, 1000), index._find_layer_first_and_end(99))
1388
        self.assertEqual((10, 1000), index._find_layer_first_and_end(999))
1389
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1390
    def test_unknown_size(self):
1391
        # We should not expand if we don't know the file size
1392
        index = self.make_index(None, 10)
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1393
        self.assertExpandOffsets([0], index, [0])
1394
        self.assertExpandOffsets([1, 4, 9], index, [1, 4, 9])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1395
1396
    def test_more_than_recommended(self):
1397
        index = self.make_index(4096*100, 2)
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1398
        self.assertExpandOffsets([1, 10], index, [1, 10])
1399
        self.assertExpandOffsets([1, 10, 20], index, [1, 10, 20])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1400
1401
    def test_read_all_from_root(self):
1402
        index = self.make_index(4096*10, 20)
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1403
        self.assertExpandOffsets(range(10), index, [0])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1404
1405
    def test_read_all_when_cached(self):
1406
        # We've read enough that we can grab all the rest in a single request
1407
        index = self.make_index(4096*10, 5)
1408
        self.prepare_index(index, node_ref_lists=0, key_length=1,
1409
                           key_count=1000, row_lengths=[1, 9],
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1410
                           cached_offsets=[0, 1, 2, 5, 6])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1411
        # It should fill the remaining nodes, regardless of the one requested
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1412
        self.assertExpandOffsets([3, 4, 7, 8, 9], index, [3])
1413
        self.assertExpandOffsets([3, 4, 7, 8, 9], index, [8])
1414
        self.assertExpandOffsets([3, 4, 7, 8, 9], index, [9])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1415
1416
    def test_no_root_node(self):
1417
        index = self.make_index(4096*10, 5)
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1418
        self.assertExpandOffsets([0], index, [0])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1419
1420
    def test_include_neighbors(self):
1421
        index = self.make_100_node_index()
1422
        # We expand in both directions, until we have at least 'recommended'
1423
        # pages
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1424
        self.assertExpandOffsets([9, 10, 11, 12, 13, 14, 15], index, [12])
1425
        self.assertExpandOffsets([88, 89, 90, 91, 92, 93, 94], index, [91])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1426
        # If we hit an 'edge' we continue in the other direction
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1427
        self.assertExpandOffsets([1, 2, 3, 4, 5, 6], index, [2])
1428
        self.assertExpandOffsets([94, 95, 96, 97, 98, 99], index, [98])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1429
1430
        # Requesting many nodes will expand all locations equally
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1431
        self.assertExpandOffsets([1, 2, 3, 80, 81, 82], index, [2, 81])
1432
        self.assertExpandOffsets([1, 2, 3, 9, 10, 11, 80, 81, 82], index,
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1433
                               [2, 10, 81])
1434
3763.8.8 by John Arbash Meinel
simple test of overlapped behavior
1435
    def test_stop_at_cached(self):
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1436
        index = self.make_100_node_index()
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1437
        self.set_cached_offsets(index, [0, 10, 19])
1438
        self.assertExpandOffsets([11, 12, 13, 14, 15, 16], index, [11])
1439
        self.assertExpandOffsets([11, 12, 13, 14, 15, 16], index, [12])
1440
        self.assertExpandOffsets([12, 13, 14, 15, 16, 17, 18], index, [15])
1441
        self.assertExpandOffsets([13, 14, 15, 16, 17, 18], index, [16])
1442
        self.assertExpandOffsets([13, 14, 15, 16, 17, 18], index, [17])
1443
        self.assertExpandOffsets([13, 14, 15, 16, 17, 18], index, [18])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1444
3763.8.8 by John Arbash Meinel
simple test of overlapped behavior
1445
    def test_cannot_fully_expand(self):
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1446
        index = self.make_100_node_index()
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1447
        self.set_cached_offsets(index, [0, 10, 12])
3763.8.7 by John Arbash Meinel
A bit of doc updates, start putting in tests for current behavior.
1448
        # We don't go into an endless loop if we are bound by cached nodes
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1449
        self.assertExpandOffsets([11], index, [11])
3763.8.8 by John Arbash Meinel
simple test of overlapped behavior
1450
1451
    def test_overlap(self):
1452
        index = self.make_100_node_index()
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1453
        self.assertExpandOffsets([10, 11, 12, 13, 14, 15], index, [12, 13])
1454
        self.assertExpandOffsets([10, 11, 12, 13, 14, 15], index, [11, 14])
3763.8.9 by John Arbash Meinel
Finish up the algorithm to stay within a given layer.
1455
1456
    def test_stay_within_layer(self):
1457
        index = self.make_1000_node_index()
1458
        # When expanding a request, we won't read nodes from the next layer
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1459
        self.assertExpandOffsets([1, 2, 3, 4], index, [2])
1460
        self.assertExpandOffsets([6, 7, 8, 9], index, [6])
1461
        self.assertExpandOffsets([6, 7, 8, 9], index, [9])
1462
        self.assertExpandOffsets([10, 11, 12, 13, 14, 15], index, [10])
1463
        self.assertExpandOffsets([10, 11, 12, 13, 14, 15, 16], index, [13])
3763.8.9 by John Arbash Meinel
Finish up the algorithm to stay within a given layer.
1464
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1465
        self.set_cached_offsets(index, [0, 4, 12])
1466
        self.assertExpandOffsets([5, 6, 7, 8, 9], index, [7])
1467
        self.assertExpandOffsets([10, 11], index, [11])
3763.8.14 by John Arbash Meinel
Add in a shortcut when we haven't cached much yet.
1468
1469
    def test_small_requests_unexpanded(self):
1470
        index = self.make_100_node_index()
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1471
        self.set_cached_offsets(index, [0])
1472
        self.assertExpandOffsets([1], index, [1])
1473
        self.assertExpandOffsets([50], index, [50])
3763.8.14 by John Arbash Meinel
Add in a shortcut when we haven't cached much yet.
1474
        # If we request more than one node, then we'll expand
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1475
        self.assertExpandOffsets([49, 50, 51, 59, 60, 61], index, [50, 60])
3763.8.14 by John Arbash Meinel
Add in a shortcut when we haven't cached much yet.
1476
1477
        # The first pass does not expand
1478
        index = self.make_1000_node_index()
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1479
        self.set_cached_offsets(index, [0])
1480
        self.assertExpandOffsets([1], index, [1])
1481
        self.set_cached_offsets(index, [0, 1])
1482
        self.assertExpandOffsets([100], index, [100])
1483
        self.set_cached_offsets(index, [0, 1, 100])
3763.8.14 by John Arbash Meinel
Add in a shortcut when we haven't cached much yet.
1484
        # But after the first depth, we will expand
3763.8.15 by John Arbash Meinel
Review comments from Martin. Code clarity/variable name/docstring updates.
1485
        self.assertExpandOffsets([2, 3, 4, 5, 6, 7], index, [2])
1486
        self.assertExpandOffsets([2, 3, 4, 5, 6, 7], index, [4])
1487
        self.set_cached_offsets(index, [0, 1, 2, 3, 4, 5, 6, 7, 100])
1488
        self.assertExpandOffsets([102, 103, 104, 105, 106, 107, 108], index,
1489
                                 [105])