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