~bzr-pqm/bzr/bzr.dev

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