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