~bzr-pqm/bzr/bzr.dev

4241.6.1 by Ian Clatworthy
chk_map code from brisbane-core
1
# Copyright (C) 2008 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
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.
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
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
"""Tests for maps built on a CHK versionedfiles facility."""
18
19
from itertools import izip
20
21
from bzrlib import (
22
    chk_map,
23
    osutils,
24
    tests,
25
    )
26
from bzrlib.chk_map import (
27
    CHKMap,
28
    InternalNode,
29
    LeafNode,
30
    Node,
31
    )
32
33
34
class TestNode(tests.TestCase):
35
36
    def assertCommonPrefix(self, expected_common, prefix, key):
37
        common = Node.common_prefix(prefix, key)
38
        self.assertTrue(len(common) <= len(prefix))
39
        self.assertTrue(len(common) <= len(key))
40
        self.assertStartsWith(prefix, common)
41
        self.assertStartsWith(key, common)
42
        self.assertEquals(expected_common, common)
43
44
    def test_common_prefix(self):
45
        self.assertCommonPrefix('beg', 'beg', 'begin')
46
47
    def test_no_common_prefix(self):
48
        self.assertCommonPrefix('', 'begin', 'end')
49
50
    def test_equal(self):
51
        self.assertCommonPrefix('begin', 'begin', 'begin')
52
53
    def test_not_a_prefix(self):
54
        self.assertCommonPrefix('b', 'begin', 'b')
55
4358.1.1 by Jelmer Vernooij
Support empty keys when looking for common prefixes in CHKMap.
56
    def test_empty(self):
57
        self.assertCommonPrefix('', '', 'end')
58
        self.assertCommonPrefix('', 'begin', '')
59
        self.assertCommonPrefix('', '', '')
60
4241.6.1 by Ian Clatworthy
chk_map code from brisbane-core
61
62
class TestCaseWithStore(tests.TestCaseWithTransport):
63
64
    def get_chk_bytes(self):
4241.6.8 by Robert Collins, John Arbash Meinel, Ian Clatworthy, Vincent Ladeuil
Add --development6-rich-root, disabling the legacy and unneeded development2 format, and activating the tests for CHK features disabled pending this format. (Robert Collins, John Arbash Meinel, Ian Clatworthy, Vincent Ladeuil)
65
        # The easiest way to get a CHK store is a development6 repository and
4241.6.1 by Ian Clatworthy
chk_map code from brisbane-core
66
        # then work with the chk_bytes attribute directly.
4241.6.8 by Robert Collins, John Arbash Meinel, Ian Clatworthy, Vincent Ladeuil
Add --development6-rich-root, disabling the legacy and unneeded development2 format, and activating the tests for CHK features disabled pending this format. (Robert Collins, John Arbash Meinel, Ian Clatworthy, Vincent Ladeuil)
67
        repo = self.make_repository(".", format="development6-rich-root")
4241.6.1 by Ian Clatworthy
chk_map code from brisbane-core
68
        repo.lock_write()
69
        self.addCleanup(repo.unlock)
70
        repo.start_write_group()
71
        self.addCleanup(repo.abort_write_group)
72
        return repo.chk_bytes
73
74
    def _get_map(self, a_dict, maximum_size=0, chk_bytes=None, key_width=1,
75
                 search_key_func=None):
76
        if chk_bytes is None:
77
            chk_bytes = self.get_chk_bytes()
78
        root_key = CHKMap.from_dict(chk_bytes, a_dict,
79
            maximum_size=maximum_size, key_width=key_width,
80
            search_key_func=search_key_func)
81
        chkmap = CHKMap(chk_bytes, root_key, search_key_func=search_key_func)
82
        return chkmap
83
84
    def read_bytes(self, chk_bytes, key):
85
        stream = chk_bytes.get_record_stream([key], 'unordered', True)
86
        record = stream.next()
87
        if record.storage_kind == 'absent':
88
            self.fail('Store does not contain the key %s' % (key,))
89
        return record.get_bytes_as("fulltext")
90
91
    def to_dict(self, node, *args):
92
        return dict(node.iteritems(*args))
93
94
95
class TestMap(TestCaseWithStore):
96
97
    def assertHasABMap(self, chk_bytes):
98
        ab_leaf_bytes = 'chkleaf:\n0\n1\n1\na\n\x001\nb\n'
99
        ab_sha1 = osutils.sha_string(ab_leaf_bytes)
100
        self.assertEqual('90986195696b177c8895d48fdb4b7f2366f798a0', ab_sha1)
101
        root_key = ('sha1:' + ab_sha1,)
102
        self.assertEqual(ab_leaf_bytes, self.read_bytes(chk_bytes, root_key))
103
        return root_key
104
105
    def assertHasEmptyMap(self, chk_bytes):
106
        empty_leaf_bytes = 'chkleaf:\n0\n1\n0\n\n'
107
        empty_sha1 = osutils.sha_string(empty_leaf_bytes)
108
        self.assertEqual('8571e09bf1bcc5b9621ce31b3d4c93d6e9a1ed26', empty_sha1)
109
        root_key = ('sha1:' + empty_sha1,)
110
        self.assertEqual(empty_leaf_bytes, self.read_bytes(chk_bytes, root_key))
111
        return root_key
112
113
    def assertMapLayoutEqual(self, map_one, map_two):
114
        """Assert that the internal structure is identical between the maps."""
115
        map_one._ensure_root()
116
        node_one_stack = [map_one._root_node]
117
        map_two._ensure_root()
118
        node_two_stack = [map_two._root_node]
119
        while node_one_stack:
120
            node_one = node_one_stack.pop()
121
            node_two = node_two_stack.pop()
122
            if node_one.__class__ != node_two.__class__:
123
                self.assertEqualDiff(map_one._dump_tree(include_keys=True),
124
                                     map_two._dump_tree(include_keys=True))
125
            self.assertEqual(node_one._search_prefix,
126
                             node_two._search_prefix)
127
            if isinstance(node_one, InternalNode):
128
                # Internal nodes must have identical references
129
                self.assertEqual(sorted(node_one._items.keys()),
130
                                 sorted(node_two._items.keys()))
131
                node_one_stack.extend([n for n, _ in
132
                                       node_one._iter_nodes(map_one._store)])
133
                node_two_stack.extend([n for n, _ in
134
                                       node_two._iter_nodes(map_two._store)])
135
            else:
136
                # Leaf nodes must have identical contents
137
                self.assertEqual(node_one._items, node_two._items)
138
        self.assertEquals([], node_two_stack)
139
140
    def assertCanonicalForm(self, chkmap):
141
        """Assert that the chkmap is in 'canonical' form.
142
143
        We do this by adding all of the key value pairs from scratch, both in
144
        forward order and reverse order, and assert that the final tree layout
145
        is identical.
146
        """
147
        items = list(chkmap.iteritems())
148
        map_forward = chk_map.CHKMap(None, None)
149
        map_forward._root_node.set_maximum_size(chkmap._root_node.maximum_size)
150
        for key, value in items:
151
            map_forward.map(key, value)
152
        self.assertMapLayoutEqual(map_forward, chkmap)
153
        map_reverse = chk_map.CHKMap(None, None)
154
        map_reverse._root_node.set_maximum_size(chkmap._root_node.maximum_size)
155
        for key, value in reversed(items):
156
            map_reverse.map(key, value)
157
        self.assertMapLayoutEqual(map_reverse, chkmap)
158
159
    def test_assert_map_layout_equal(self):
160
        store = self.get_chk_bytes()
161
        map_one = CHKMap(store, None)
162
        map_one._root_node.set_maximum_size(20)
163
        map_two = CHKMap(store, None)
164
        map_two._root_node.set_maximum_size(20)
165
        self.assertMapLayoutEqual(map_one, map_two)
166
        map_one.map('aaa', 'value')
167
        self.assertRaises(AssertionError,
168
            self.assertMapLayoutEqual, map_one, map_two)
169
        map_two.map('aaa', 'value')
170
        self.assertMapLayoutEqual(map_one, map_two)
171
        # Split the tree, so we ensure that internal nodes and leaf nodes are
172
        # properly checked
173
        map_one.map('aab', 'value')
174
        self.assertIsInstance(map_one._root_node, InternalNode)
175
        self.assertRaises(AssertionError,
176
            self.assertMapLayoutEqual, map_one, map_two)
177
        map_two.map('aab', 'value')
178
        self.assertMapLayoutEqual(map_one, map_two)
179
        map_one.map('aac', 'value')
180
        self.assertRaises(AssertionError,
181
            self.assertMapLayoutEqual, map_one, map_two)
182
        self.assertCanonicalForm(map_one)
183
184
    def test_from_dict_empty(self):
185
        chk_bytes = self.get_chk_bytes()
186
        root_key = CHKMap.from_dict(chk_bytes, {})
187
        # Check the data was saved and inserted correctly.
188
        expected_root_key = self.assertHasEmptyMap(chk_bytes)
189
        self.assertEqual(expected_root_key, root_key)
190
191
    def test_from_dict_ab(self):
192
        chk_bytes = self.get_chk_bytes()
193
        root_key = CHKMap.from_dict(chk_bytes, {"a": "b"})
194
        # Check the data was saved and inserted correctly.
195
        expected_root_key = self.assertHasABMap(chk_bytes)
196
        self.assertEqual(expected_root_key, root_key)
197
198
    def test_apply_empty_ab(self):
199
        # applying a delta (None, "a", "b") to an empty chkmap generates the
200
        # same map as from_dict_ab.
201
        chk_bytes = self.get_chk_bytes()
202
        root_key = CHKMap.from_dict(chk_bytes, {})
203
        chkmap = CHKMap(chk_bytes, root_key)
204
        new_root = chkmap.apply_delta([(None, "a", "b")])
205
        # Check the data was saved and inserted correctly.
206
        expected_root_key = self.assertHasABMap(chk_bytes)
207
        self.assertEqual(expected_root_key, new_root)
208
        # The update should have left us with an in memory root node, with an
209
        # updated key.
210
        self.assertEqual(new_root, chkmap._root_node._key)
211
212
    def test_apply_ab_empty(self):
213
        # applying a delta ("a", None, None) to a map with 'a' in it generates
214
        # an empty map.
215
        chk_bytes = self.get_chk_bytes()
216
        root_key = CHKMap.from_dict(chk_bytes, {("a",):"b"})
217
        chkmap = CHKMap(chk_bytes, root_key)
218
        new_root = chkmap.apply_delta([(("a",), None, None)])
219
        # Check the data was saved and inserted correctly.
220
        expected_root_key = self.assertHasEmptyMap(chk_bytes)
221
        self.assertEqual(expected_root_key, new_root)
222
        # The update should have left us with an in memory root node, with an
223
        # updated key.
224
        self.assertEqual(new_root, chkmap._root_node._key)
225
226
    def test_apply_delta_is_deterministic(self):
227
        chk_bytes = self.get_chk_bytes()
228
        chkmap1 = CHKMap(chk_bytes, None)
229
        chkmap1._root_node.set_maximum_size(10)
230
        chkmap1.apply_delta([(None, ('aaa',), 'common'),
231
                             (None, ('bba',), 'target2'),
232
                             (None, ('bbb',), 'common')])
233
        root_key1 = chkmap1._save()
234
        self.assertCanonicalForm(chkmap1)
235
236
        chkmap2 = CHKMap(chk_bytes, None)
237
        chkmap2._root_node.set_maximum_size(10)
238
        chkmap2.apply_delta([(None, ('bbb',), 'common'),
239
                             (None, ('bba',), 'target2'),
240
                             (None, ('aaa',), 'common')])
241
        root_key2 = chkmap2._save()
242
        self.assertEqualDiff(chkmap1._dump_tree(include_keys=True),
243
                             chkmap2._dump_tree(include_keys=True))
244
        self.assertEqual(root_key1, root_key2)
245
        self.assertCanonicalForm(chkmap2)
246
247
    def test_stable_splitting(self):
248
        store = self.get_chk_bytes()
249
        chkmap = CHKMap(store, None)
250
        # Should fit 2 keys per LeafNode
251
        chkmap._root_node.set_maximum_size(35)
252
        chkmap.map(('aaa',), 'v')
253
        self.assertEqualDiff("'' LeafNode\n"
254
                             "      ('aaa',) 'v'\n",
255
                             chkmap._dump_tree())
256
        chkmap.map(('aab',), 'v')
257
        self.assertEqualDiff("'' LeafNode\n"
258
                             "      ('aaa',) 'v'\n"
259
                             "      ('aab',) 'v'\n",
260
                             chkmap._dump_tree())
261
        self.assertCanonicalForm(chkmap)
262
263
        # Creates a new internal node, and splits the others into leaves
264
        chkmap.map(('aac',), 'v')
265
        self.assertEqualDiff("'' InternalNode\n"
266
                             "  'aaa' LeafNode\n"
267
                             "      ('aaa',) 'v'\n"
268
                             "  'aab' LeafNode\n"
269
                             "      ('aab',) 'v'\n"
270
                             "  'aac' LeafNode\n"
271
                             "      ('aac',) 'v'\n",
272
                             chkmap._dump_tree())
273
        self.assertCanonicalForm(chkmap)
274
275
        # Splits again, because it can't fit in the current structure
276
        chkmap.map(('bbb',), 'v')
277
        self.assertEqualDiff("'' InternalNode\n"
278
                             "  'a' InternalNode\n"
279
                             "    'aaa' LeafNode\n"
280
                             "      ('aaa',) 'v'\n"
281
                             "    'aab' LeafNode\n"
282
                             "      ('aab',) 'v'\n"
283
                             "    'aac' LeafNode\n"
284
                             "      ('aac',) 'v'\n"
285
                             "  'b' LeafNode\n"
286
                             "      ('bbb',) 'v'\n",
287
                             chkmap._dump_tree())
288
        self.assertCanonicalForm(chkmap)
289
290
    def test_map_splits_with_longer_key(self):
291
        store = self.get_chk_bytes()
292
        chkmap = CHKMap(store, None)
293
        # Should fit 1 key per LeafNode
294
        chkmap._root_node.set_maximum_size(10)
295
        chkmap.map(('aaa',), 'v')
296
        chkmap.map(('aaaa',), 'v')
297
        self.assertCanonicalForm(chkmap)
298
        self.assertIsInstance(chkmap._root_node, InternalNode)
299
300
    def test_with_linefeed_in_key(self):
301
        store = self.get_chk_bytes()
302
        chkmap = CHKMap(store, None)
303
        # Should fit 1 key per LeafNode
304
        chkmap._root_node.set_maximum_size(10)
305
        chkmap.map(('a\ra',), 'val1')
306
        chkmap.map(('a\rb',), 'val2')
307
        chkmap.map(('ac',), 'val3')
308
        self.assertCanonicalForm(chkmap)
309
        self.assertEqualDiff("'' InternalNode\n"
310
                             "  'a\\r' InternalNode\n"
311
                             "    'a\\ra' LeafNode\n"
312
                             "      ('a\\ra',) 'val1'\n"
313
                             "    'a\\rb' LeafNode\n"
314
                             "      ('a\\rb',) 'val2'\n"
315
                             "  'ac' LeafNode\n"
316
                             "      ('ac',) 'val3'\n",
317
                             chkmap._dump_tree())
318
        # We should also successfully serialise and deserialise these items
319
        root_key = chkmap._save()
320
        chkmap = CHKMap(store, root_key)
321
        self.assertEqualDiff("'' InternalNode\n"
322
                             "  'a\\r' InternalNode\n"
323
                             "    'a\\ra' LeafNode\n"
324
                             "      ('a\\ra',) 'val1'\n"
325
                             "    'a\\rb' LeafNode\n"
326
                             "      ('a\\rb',) 'val2'\n"
327
                             "  'ac' LeafNode\n"
328
                             "      ('ac',) 'val3'\n",
329
                             chkmap._dump_tree())
330
331
    def test_deep_splitting(self):
332
        store = self.get_chk_bytes()
333
        chkmap = CHKMap(store, None)
334
        # Should fit 2 keys per LeafNode
335
        chkmap._root_node.set_maximum_size(40)
336
        chkmap.map(('aaaaaaaa',), 'v')
337
        chkmap.map(('aaaaabaa',), 'v')
338
        self.assertEqualDiff("'' LeafNode\n"
339
                             "      ('aaaaaaaa',) 'v'\n"
340
                             "      ('aaaaabaa',) 'v'\n",
341
                             chkmap._dump_tree())
342
        chkmap.map(('aaabaaaa',), 'v')
343
        chkmap.map(('aaababaa',), 'v')
344
        self.assertEqualDiff("'' InternalNode\n"
345
                             "  'aaaa' LeafNode\n"
346
                             "      ('aaaaaaaa',) 'v'\n"
347
                             "      ('aaaaabaa',) 'v'\n"
348
                             "  'aaab' LeafNode\n"
349
                             "      ('aaabaaaa',) 'v'\n"
350
                             "      ('aaababaa',) 'v'\n",
351
                             chkmap._dump_tree())
352
        chkmap.map(('aaabacaa',), 'v')
353
        chkmap.map(('aaabadaa',), 'v')
354
        self.assertEqualDiff("'' InternalNode\n"
355
                             "  'aaaa' LeafNode\n"
356
                             "      ('aaaaaaaa',) 'v'\n"
357
                             "      ('aaaaabaa',) 'v'\n"
358
                             "  'aaab' InternalNode\n"
359
                             "    'aaabaa' LeafNode\n"
360
                             "      ('aaabaaaa',) 'v'\n"
361
                             "    'aaabab' LeafNode\n"
362
                             "      ('aaababaa',) 'v'\n"
363
                             "    'aaabac' LeafNode\n"
364
                             "      ('aaabacaa',) 'v'\n"
365
                             "    'aaabad' LeafNode\n"
366
                             "      ('aaabadaa',) 'v'\n",
367
                             chkmap._dump_tree())
368
        chkmap.map(('aaababba',), 'val')
369
        chkmap.map(('aaababca',), 'val')
370
        self.assertEqualDiff("'' InternalNode\n"
371
                             "  'aaaa' LeafNode\n"
372
                             "      ('aaaaaaaa',) 'v'\n"
373
                             "      ('aaaaabaa',) 'v'\n"
374
                             "  'aaab' InternalNode\n"
375
                             "    'aaabaa' LeafNode\n"
376
                             "      ('aaabaaaa',) 'v'\n"
377
                             "    'aaabab' InternalNode\n"
378
                             "      'aaababa' LeafNode\n"
379
                             "      ('aaababaa',) 'v'\n"
380
                             "      'aaababb' LeafNode\n"
381
                             "      ('aaababba',) 'val'\n"
382
                             "      'aaababc' LeafNode\n"
383
                             "      ('aaababca',) 'val'\n"
384
                             "    'aaabac' LeafNode\n"
385
                             "      ('aaabacaa',) 'v'\n"
386
                             "    'aaabad' LeafNode\n"
387
                             "      ('aaabadaa',) 'v'\n",
388
                             chkmap._dump_tree())
389
        # Now we add a node that should fit around an existing InternalNode,
390
        # but has a slightly different key prefix, which causes a new
391
        # InternalNode split
392
        chkmap.map(('aaabDaaa',), 'v')
393
        self.assertEqualDiff("'' InternalNode\n"
394
                             "  'aaaa' LeafNode\n"
395
                             "      ('aaaaaaaa',) 'v'\n"
396
                             "      ('aaaaabaa',) 'v'\n"
397
                             "  'aaab' InternalNode\n"
398
                             "    'aaabD' LeafNode\n"
399
                             "      ('aaabDaaa',) 'v'\n"
400
                             "    'aaaba' InternalNode\n"
401
                             "      'aaabaa' LeafNode\n"
402
                             "      ('aaabaaaa',) 'v'\n"
403
                             "      'aaabab' InternalNode\n"
404
                             "        'aaababa' LeafNode\n"
405
                             "      ('aaababaa',) 'v'\n"
406
                             "        'aaababb' LeafNode\n"
407
                             "      ('aaababba',) 'val'\n"
408
                             "        'aaababc' LeafNode\n"
409
                             "      ('aaababca',) 'val'\n"
410
                             "      'aaabac' LeafNode\n"
411
                             "      ('aaabacaa',) 'v'\n"
412
                             "      'aaabad' LeafNode\n"
413
                             "      ('aaabadaa',) 'v'\n",
414
                             chkmap._dump_tree())
415
416
    def test_map_collapses_if_size_changes(self):
417
        store = self.get_chk_bytes()
418
        chkmap = CHKMap(store, None)
419
        # Should fit 2 keys per LeafNode
420
        chkmap._root_node.set_maximum_size(35)
421
        chkmap.map(('aaa',), 'v')
422
        chkmap.map(('aab',), 'very long value that splits')
423
        self.assertEqualDiff("'' InternalNode\n"
424
                             "  'aaa' LeafNode\n"
425
                             "      ('aaa',) 'v'\n"
426
                             "  'aab' LeafNode\n"
427
                             "      ('aab',) 'very long value that splits'\n",
428
                             chkmap._dump_tree())
429
        self.assertCanonicalForm(chkmap)
430
        # Now changing the value to something small should cause a rebuild
431
        chkmap.map(('aab',), 'v')
432
        self.assertEqualDiff("'' LeafNode\n"
433
                             "      ('aaa',) 'v'\n"
434
                             "      ('aab',) 'v'\n",
435
                             chkmap._dump_tree())
436
        self.assertCanonicalForm(chkmap)
437
438
    def test_map_double_deep_collapses(self):
439
        store = self.get_chk_bytes()
440
        chkmap = CHKMap(store, None)
441
        # Should fit 3 small keys per LeafNode
442
        chkmap._root_node.set_maximum_size(40)
443
        chkmap.map(('aaa',), 'v')
444
        chkmap.map(('aab',), 'very long value that splits')
445
        chkmap.map(('abc',), 'v')
446
        self.assertEqualDiff("'' InternalNode\n"
447
                             "  'aa' InternalNode\n"
448
                             "    'aaa' LeafNode\n"
449
                             "      ('aaa',) 'v'\n"
450
                             "    'aab' LeafNode\n"
451
                             "      ('aab',) 'very long value that splits'\n"
452
                             "  'ab' LeafNode\n"
453
                             "      ('abc',) 'v'\n",
454
                             chkmap._dump_tree())
455
        chkmap.map(('aab',), 'v')
456
        self.assertCanonicalForm(chkmap)
457
        self.assertEqualDiff("'' LeafNode\n"
458
                             "      ('aaa',) 'v'\n"
459
                             "      ('aab',) 'v'\n"
460
                             "      ('abc',) 'v'\n",
461
                             chkmap._dump_tree())
462
463
    def test_stable_unmap(self):
464
        store = self.get_chk_bytes()
465
        chkmap = CHKMap(store, None)
466
        # Should fit 2 keys per LeafNode
467
        chkmap._root_node.set_maximum_size(35)
468
        chkmap.map(('aaa',), 'v')
469
        chkmap.map(('aab',), 'v')
470
        self.assertEqualDiff("'' LeafNode\n"
471
                             "      ('aaa',) 'v'\n"
472
                             "      ('aab',) 'v'\n",
473
                             chkmap._dump_tree())
474
        # Creates a new internal node, and splits the others into leaves
475
        chkmap.map(('aac',), 'v')
476
        self.assertEqualDiff("'' InternalNode\n"
477
                             "  'aaa' LeafNode\n"
478
                             "      ('aaa',) 'v'\n"
479
                             "  'aab' LeafNode\n"
480
                             "      ('aab',) 'v'\n"
481
                             "  'aac' LeafNode\n"
482
                             "      ('aac',) 'v'\n",
483
                             chkmap._dump_tree())
484
        self.assertCanonicalForm(chkmap)
485
        # Now lets unmap one of the keys, and assert that we collapse the
486
        # structures.
487
        chkmap.unmap(('aac',))
488
        self.assertEqualDiff("'' LeafNode\n"
489
                             "      ('aaa',) 'v'\n"
490
                             "      ('aab',) 'v'\n",
491
                             chkmap._dump_tree())
492
        self.assertCanonicalForm(chkmap)
493
494
    def test_unmap_double_deep(self):
495
        store = self.get_chk_bytes()
496
        chkmap = CHKMap(store, None)
497
        # Should fit 3 keys per LeafNode
498
        chkmap._root_node.set_maximum_size(40)
499
        chkmap.map(('aaa',), 'v')
500
        chkmap.map(('aaab',), 'v')
501
        chkmap.map(('aab',), 'very long value')
502
        chkmap.map(('abc',), 'v')
503
        self.assertEqualDiff("'' InternalNode\n"
504
                             "  'aa' InternalNode\n"
505
                             "    'aaa' LeafNode\n"
506
                             "      ('aaa',) 'v'\n"
507
                             "      ('aaab',) 'v'\n"
508
                             "    'aab' LeafNode\n"
509
                             "      ('aab',) 'very long value'\n"
510
                             "  'ab' LeafNode\n"
511
                             "      ('abc',) 'v'\n",
512
                             chkmap._dump_tree())
513
        # Removing the 'aab' key should cause everything to collapse back to a
514
        # single node
515
        chkmap.unmap(('aab',))
516
        self.assertEqualDiff("'' LeafNode\n"
517
                             "      ('aaa',) 'v'\n"
518
                             "      ('aaab',) 'v'\n"
519
                             "      ('abc',) 'v'\n",
520
                             chkmap._dump_tree())
521
522
    def test_unmap_double_deep_non_empty_leaf(self):
523
        store = self.get_chk_bytes()
524
        chkmap = CHKMap(store, None)
525
        # Should fit 3 keys per LeafNode
526
        chkmap._root_node.set_maximum_size(40)
527
        chkmap.map(('aaa',), 'v')
528
        chkmap.map(('aab',), 'long value')
529
        chkmap.map(('aabb',), 'v')
530
        chkmap.map(('abc',), 'v')
531
        self.assertEqualDiff("'' InternalNode\n"
532
                             "  'aa' InternalNode\n"
533
                             "    'aaa' LeafNode\n"
534
                             "      ('aaa',) 'v'\n"
535
                             "    'aab' LeafNode\n"
536
                             "      ('aab',) 'long value'\n"
537
                             "      ('aabb',) 'v'\n"
538
                             "  'ab' LeafNode\n"
539
                             "      ('abc',) 'v'\n",
540
                             chkmap._dump_tree())
541
        # Removing the 'aab' key should cause everything to collapse back to a
542
        # single node
543
        chkmap.unmap(('aab',))
544
        self.assertEqualDiff("'' LeafNode\n"
545
                             "      ('aaa',) 'v'\n"
546
                             "      ('aabb',) 'v'\n"
547
                             "      ('abc',) 'v'\n",
548
                             chkmap._dump_tree())
549
550
    def test_unmap_with_known_internal_node_doesnt_page(self):
551
        store = self.get_chk_bytes()
552
        chkmap = CHKMap(store, None)
553
        # Should fit 3 keys per LeafNode
554
        chkmap._root_node.set_maximum_size(30)
555
        chkmap.map(('aaa',), 'v')
556
        chkmap.map(('aab',), 'v')
557
        chkmap.map(('aac',), 'v')
558
        chkmap.map(('abc',), 'v')
559
        chkmap.map(('acd',), 'v')
560
        self.assertEqualDiff("'' InternalNode\n"
561
                             "  'aa' InternalNode\n"
562
                             "    'aaa' LeafNode\n"
563
                             "      ('aaa',) 'v'\n"
564
                             "    'aab' LeafNode\n"
565
                             "      ('aab',) 'v'\n"
566
                             "    'aac' LeafNode\n"
567
                             "      ('aac',) 'v'\n"
568
                             "  'ab' LeafNode\n"
569
                             "      ('abc',) 'v'\n"
570
                             "  'ac' LeafNode\n"
571
                             "      ('acd',) 'v'\n",
572
                             chkmap._dump_tree())
573
        # Save everything to the map, and start over
574
        chkmap = CHKMap(store, chkmap._save())
575
        # Mapping an 'aa' key loads the internal node, but should not map the
576
        # 'ab' and 'ac' nodes
577
        chkmap.map(('aad',), 'v')
578
        self.assertIsInstance(chkmap._root_node._items['aa'], InternalNode)
579
        self.assertIsInstance(chkmap._root_node._items['ab'], tuple)
580
        self.assertIsInstance(chkmap._root_node._items['ac'], tuple)
581
        # Unmapping 'acd' can notice that 'aa' is an InternalNode and not have
582
        # to map in 'ab'
583
        chkmap.unmap(('acd',))
584
        self.assertIsInstance(chkmap._root_node._items['aa'], InternalNode)
585
        self.assertIsInstance(chkmap._root_node._items['ab'], tuple)
586
587
    def test_unmap_without_fitting_doesnt_page_in(self):
588
        store = self.get_chk_bytes()
589
        chkmap = CHKMap(store, None)
590
        # Should fit 2 keys per LeafNode
591
        chkmap._root_node.set_maximum_size(20)
592
        chkmap.map(('aaa',), 'v')
593
        chkmap.map(('aab',), 'v')
594
        self.assertEqualDiff("'' InternalNode\n"
595
                             "  'aaa' LeafNode\n"
596
                             "      ('aaa',) 'v'\n"
597
                             "  'aab' LeafNode\n"
598
                             "      ('aab',) 'v'\n",
599
                             chkmap._dump_tree())
600
        # Save everything to the map, and start over
601
        chkmap = CHKMap(store, chkmap._save())
602
        chkmap.map(('aac',), 'v')
603
        chkmap.map(('aad',), 'v')
604
        chkmap.map(('aae',), 'v')
605
        chkmap.map(('aaf',), 'v')
606
        # At this point, the previous nodes should not be paged in, but the
607
        # newly added nodes would be
608
        self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)
609
        self.assertIsInstance(chkmap._root_node._items['aab'], tuple)
610
        self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)
611
        self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
612
        self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)
613
        self.assertIsInstance(chkmap._root_node._items['aaf'], LeafNode)
614
        # Now unmapping one of the new nodes will use only the already-paged-in
615
        # nodes to determine that we don't need to do more.
616
        chkmap.unmap(('aaf',))
617
        self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)
618
        self.assertIsInstance(chkmap._root_node._items['aab'], tuple)
619
        self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)
620
        self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
621
        self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)
622
623
    def test_unmap_pages_in_if_necessary(self):
624
        store = self.get_chk_bytes()
625
        chkmap = CHKMap(store, None)
626
        # Should fit 2 keys per LeafNode
627
        chkmap._root_node.set_maximum_size(30)
628
        chkmap.map(('aaa',), 'val')
629
        chkmap.map(('aab',), 'val')
630
        chkmap.map(('aac',), 'val')
631
        self.assertEqualDiff("'' InternalNode\n"
632
                             "  'aaa' LeafNode\n"
633
                             "      ('aaa',) 'val'\n"
634
                             "  'aab' LeafNode\n"
635
                             "      ('aab',) 'val'\n"
636
                             "  'aac' LeafNode\n"
637
                             "      ('aac',) 'val'\n",
638
                             chkmap._dump_tree())
639
        root_key = chkmap._save()
640
        # Save everything to the map, and start over
641
        chkmap = CHKMap(store, root_key)
642
        chkmap.map(('aad',), 'v')
643
        # At this point, the previous nodes should not be paged in, but the
644
        # newly added node would be
645
        self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)
646
        self.assertIsInstance(chkmap._root_node._items['aab'], tuple)
647
        self.assertIsInstance(chkmap._root_node._items['aac'], tuple)
648
        self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
649
        # Unmapping the new node will check the existing nodes to see if they
650
        # would fit.
651
        # Clear the page cache so we ensure we have to read all the children
652
        chk_map._page_cache.clear()
653
        chkmap.unmap(('aad',))
654
        self.assertIsInstance(chkmap._root_node._items['aaa'], LeafNode)
655
        self.assertIsInstance(chkmap._root_node._items['aab'], LeafNode)
656
        self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)
657
658
    def test_unmap_pages_in_from_page_cache(self):
659
        store = self.get_chk_bytes()
660
        chkmap = CHKMap(store, None)
661
        # Should fit 2 keys per LeafNode
662
        chkmap._root_node.set_maximum_size(30)
663
        chkmap.map(('aaa',), 'val')
664
        chkmap.map(('aab',), 'val')
665
        chkmap.map(('aac',), 'val')
666
        root_key = chkmap._save()
667
        # Save everything to the map, and start over
668
        chkmap = CHKMap(store, root_key)
669
        chkmap.map(('aad',), 'val')
670
        self.assertEqualDiff("'' InternalNode\n"
671
                             "  'aaa' LeafNode\n"
672
                             "      ('aaa',) 'val'\n"
673
                             "  'aab' LeafNode\n"
674
                             "      ('aab',) 'val'\n"
675
                             "  'aac' LeafNode\n"
676
                             "      ('aac',) 'val'\n"
677
                             "  'aad' LeafNode\n"
678
                             "      ('aad',) 'val'\n",
679
                             chkmap._dump_tree())
680
        # Save everything to the map, start over after _dump_tree
681
        chkmap = CHKMap(store, root_key)
682
        chkmap.map(('aad',), 'v')
683
        # At this point, the previous nodes should not be paged in, but the
684
        # newly added node would be
685
        self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)
686
        self.assertIsInstance(chkmap._root_node._items['aab'], tuple)
687
        self.assertIsInstance(chkmap._root_node._items['aac'], tuple)
688
        self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
689
        # Now clear the page cache, and only include 2 of the children in the
690
        # cache
691
        aab_key = chkmap._root_node._items['aab']
692
        aab_bytes = chk_map._page_cache[aab_key]
693
        aac_key = chkmap._root_node._items['aac']
694
        aac_bytes = chk_map._page_cache[aac_key]
695
        chk_map._page_cache.clear()
696
        chk_map._page_cache[aab_key] = aab_bytes
697
        chk_map._page_cache[aac_key] = aac_bytes
698
699
        # Unmapping the new node will check the nodes from the page cache
700
        # first, and not have to read in 'aaa'
701
        chkmap.unmap(('aad',))
702
        self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)
703
        self.assertIsInstance(chkmap._root_node._items['aab'], LeafNode)
704
        self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)
705
706
    def test_unmap_uses_existing_items(self):
707
        store = self.get_chk_bytes()
708
        chkmap = CHKMap(store, None)
709
        # Should fit 2 keys per LeafNode
710
        chkmap._root_node.set_maximum_size(30)
711
        chkmap.map(('aaa',), 'val')
712
        chkmap.map(('aab',), 'val')
713
        chkmap.map(('aac',), 'val')
714
        root_key = chkmap._save()
715
        # Save everything to the map, and start over
716
        chkmap = CHKMap(store, root_key)
717
        chkmap.map(('aad',), 'val')
718
        chkmap.map(('aae',), 'val')
719
        chkmap.map(('aaf',), 'val')
720
        # At this point, the previous nodes should not be paged in, but the
721
        # newly added node would be
722
        self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)
723
        self.assertIsInstance(chkmap._root_node._items['aab'], tuple)
724
        self.assertIsInstance(chkmap._root_node._items['aac'], tuple)
725
        self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
726
        self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)
727
        self.assertIsInstance(chkmap._root_node._items['aaf'], LeafNode)
728
729
        # Unmapping a new node will see the other nodes that are already in
730
        # memory, and not need to page in anything else
731
        chkmap.unmap(('aad',))
732
        self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)
733
        self.assertIsInstance(chkmap._root_node._items['aab'], tuple)
734
        self.assertIsInstance(chkmap._root_node._items['aac'], tuple)
735
        self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)
736
        self.assertIsInstance(chkmap._root_node._items['aaf'], LeafNode)
737
738
    def test_iter_changes_empty_ab(self):
739
        # Asking for changes between an empty dict to a dict with keys returns
740
        # all the keys.
741
        basis = self._get_map({}, maximum_size=10)
742
        target = self._get_map(
743
            {('a',): 'content here', ('b',): 'more content'},
744
            chk_bytes=basis._store, maximum_size=10)
745
        self.assertEqual([(('a',), None, 'content here'),
746
            (('b',), None, 'more content')],
747
            sorted(list(target.iter_changes(basis))))
748
749
    def test_iter_changes_ab_empty(self):
750
        # Asking for changes between a dict with keys to an empty dict returns
751
        # all the keys.
752
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
753
            maximum_size=10)
754
        target = self._get_map({}, chk_bytes=basis._store, maximum_size=10)
755
        self.assertEqual([(('a',), 'content here', None),
756
            (('b',), 'more content', None)],
757
            sorted(list(target.iter_changes(basis))))
758
759
    def test_iter_changes_empty_empty_is_empty(self):
760
        basis = self._get_map({}, maximum_size=10)
761
        target = self._get_map({}, chk_bytes=basis._store, maximum_size=10)
762
        self.assertEqual([], sorted(list(target.iter_changes(basis))))
763
764
    def test_iter_changes_ab_ab_is_empty(self):
765
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
766
            maximum_size=10)
767
        target = self._get_map(
768
            {('a',): 'content here', ('b',): 'more content'},
769
            chk_bytes=basis._store, maximum_size=10)
770
        self.assertEqual([], sorted(list(target.iter_changes(basis))))
771
772
    def test_iter_changes_ab_ab_nodes_not_loaded(self):
773
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
774
            maximum_size=10)
775
        target = self._get_map(
776
            {('a',): 'content here', ('b',): 'more content'},
777
            chk_bytes=basis._store, maximum_size=10)
778
        list(target.iter_changes(basis))
779
        self.assertIsInstance(target._root_node, tuple)
780
        self.assertIsInstance(basis._root_node, tuple)
781
782
    def test_iter_changes_ab_ab_changed_values_shown(self):
783
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
784
            maximum_size=10)
785
        target = self._get_map(
786
            {('a',): 'content here', ('b',): 'different content'},
787
            chk_bytes=basis._store, maximum_size=10)
788
        result = sorted(list(target.iter_changes(basis)))
789
        self.assertEqual([(('b',), 'more content', 'different content')],
790
            result)
791
792
    def test_iter_changes_mixed_node_length(self):
793
        # When one side has different node lengths than the other, common
794
        # but different keys still need to be show, and new-and-old included
795
        # appropriately.
796
        # aaa - common unaltered
797
        # aab - common altered
798
        # b - basis only
799
        # at - target only
800
        # we expect:
801
        # aaa to be not loaded (later test)
802
        # aab, b, at to be returned.
803
        # basis splits at byte 0,1,2, aaa is commonb is basis only
804
        basis_dict = {('aaa',): 'foo bar',
805
            ('aab',): 'common altered a', ('b',): 'foo bar b'}
806
        # target splits at byte 1,2, at is target only
807
        target_dict = {('aaa',): 'foo bar',
808
            ('aab',): 'common altered b', ('at',): 'foo bar t'}
809
        changes = [
810
            (('aab',), 'common altered a', 'common altered b'),
811
            (('at',), None, 'foo bar t'),
812
            (('b',), 'foo bar b', None),
813
            ]
814
        basis = self._get_map(basis_dict, maximum_size=10)
815
        target = self._get_map(target_dict, maximum_size=10,
816
            chk_bytes=basis._store)
817
        self.assertEqual(changes, sorted(list(target.iter_changes(basis))))
818
819
    def test_iter_changes_common_pages_not_loaded(self):
820
        # aaa - common unaltered
821
        # aab - common altered
822
        # b - basis only
823
        # at - target only
824
        # we expect:
825
        # aaa to be not loaded
826
        # aaa not to be in result.
827
        basis_dict = {('aaa',): 'foo bar',
828
            ('aab',): 'common altered a', ('b',): 'foo bar b'}
829
        # target splits at byte 1, at is target only
830
        target_dict = {('aaa',): 'foo bar',
831
            ('aab',): 'common altered b', ('at',): 'foo bar t'}
832
        basis = self._get_map(basis_dict, maximum_size=10)
833
        target = self._get_map(target_dict, maximum_size=10,
834
            chk_bytes=basis._store)
835
        basis_get = basis._store.get_record_stream
836
        def get_record_stream(keys, order, fulltext):
837
            if ('sha1:1adf7c0d1b9140ab5f33bb64c6275fa78b1580b7',) in keys:
838
                self.fail("'aaa' pointer was followed %r" % keys)
839
            return basis_get(keys, order, fulltext)
840
        basis._store.get_record_stream = get_record_stream
841
        result = sorted(list(target.iter_changes(basis)))
842
        for change in result:
843
            if change[0] == ('aaa',):
844
                self.fail("Found unexpected change: %s" % change)
845
846
    def test_iter_changes_unchanged_keys_in_multi_key_leafs_ignored(self):
847
        # Within a leaf there are no hash's to exclude keys, make sure multi
848
        # value leaf nodes are handled well.
849
        basis_dict = {('aaa',): 'foo bar',
850
            ('aab',): 'common altered a', ('b',): 'foo bar b'}
851
        target_dict = {('aaa',): 'foo bar',
852
            ('aab',): 'common altered b', ('at',): 'foo bar t'}
853
        changes = [
854
            (('aab',), 'common altered a', 'common altered b'),
855
            (('at',), None, 'foo bar t'),
856
            (('b',), 'foo bar b', None),
857
            ]
858
        basis = self._get_map(basis_dict)
859
        target = self._get_map(target_dict, chk_bytes=basis._store)
860
        self.assertEqual(changes, sorted(list(target.iter_changes(basis))))
861
862
    def test_iteritems_empty(self):
863
        chk_bytes = self.get_chk_bytes()
864
        root_key = CHKMap.from_dict(chk_bytes, {})
865
        chkmap = CHKMap(chk_bytes, root_key)
866
        self.assertEqual([], list(chkmap.iteritems()))
867
868
    def test_iteritems_two_items(self):
869
        chk_bytes = self.get_chk_bytes()
870
        root_key = CHKMap.from_dict(chk_bytes,
871
            {"a":"content here", "b":"more content"})
872
        chkmap = CHKMap(chk_bytes, root_key)
873
        self.assertEqual([(("a",), "content here"), (("b",), "more content")],
874
            sorted(list(chkmap.iteritems())))
875
876
    def test_iteritems_selected_one_of_two_items(self):
877
        chkmap = self._get_map( {("a",):"content here", ("b",):"more content"})
878
        self.assertEqual({("a",): "content here"},
879
            self.to_dict(chkmap, [("a",)]))
880
881
    def test_iteritems_keys_prefixed_by_2_width_nodes(self):
882
        chkmap = self._get_map(
883
            {("a","a"):"content here", ("a", "b",):"more content",
884
             ("b", ""): 'boring content'},
885
            maximum_size=10, key_width=2)
886
        self.assertEqual(
887
            {("a", "a"): "content here", ("a", "b"): 'more content'},
888
            self.to_dict(chkmap, [("a",)]))
889
890
    def test_iteritems_keys_prefixed_by_2_width_nodes_hashed(self):
891
        search_key_func = chk_map.search_key_registry.get('hash-16-way')
892
        self.assertEqual('E8B7BE43\x00E8B7BE43', search_key_func(('a', 'a')))
893
        self.assertEqual('E8B7BE43\x0071BEEFF9', search_key_func(('a', 'b')))
894
        self.assertEqual('71BEEFF9\x0000000000', search_key_func(('b', '')))
895
        chkmap = self._get_map(
896
            {("a","a"):"content here", ("a", "b",):"more content",
897
             ("b", ""): 'boring content'},
898
            maximum_size=10, key_width=2, search_key_func=search_key_func)
899
        self.assertEqual(
900
            {("a", "a"): "content here", ("a", "b"): 'more content'},
901
            self.to_dict(chkmap, [("a",)]))
902
903
    def test_iteritems_keys_prefixed_by_2_width_one_leaf(self):
904
        chkmap = self._get_map(
905
            {("a","a"):"content here", ("a", "b",):"more content",
906
             ("b", ""): 'boring content'}, key_width=2)
907
        self.assertEqual(
908
            {("a", "a"): "content here", ("a", "b"): 'more content'},
909
            self.to_dict(chkmap, [("a",)]))
910
911
    def test___len__empty(self):
912
        chkmap = self._get_map({})
913
        self.assertEqual(0, len(chkmap))
914
915
    def test___len__2(self):
916
        chkmap = self._get_map({("foo",):"bar", ("gam",):"quux"})
917
        self.assertEqual(2, len(chkmap))
918
919
    def test_max_size_100_bytes_new(self):
920
        # When there is a 100 byte upper node limit, a tree is formed.
921
        chkmap = self._get_map({("k1"*50,):"v1", ("k2"*50,):"v2"}, maximum_size=100)
922
        # We expect three nodes:
923
        # A root, with two children, and with two key prefixes - k1 to one, and
924
        # k2 to the other as our node splitting is only just being developed.
925
        # The maximum size should be embedded
926
        chkmap._ensure_root()
927
        self.assertEqual(100, chkmap._root_node.maximum_size)
928
        self.assertEqual(1, chkmap._root_node._key_width)
929
        # There should be two child nodes, and prefix of 2(bytes):
930
        self.assertEqual(2, len(chkmap._root_node._items))
931
        self.assertEqual("k", chkmap._root_node._compute_search_prefix())
932
        # The actual nodes pointed at will change as serialisers change; so
933
        # here we test that the key prefix is correct; then load the nodes and
934
        # check they have the right pointed at key; whether they have the
935
        # pointed at value inline or not is also unrelated to this test so we
936
        # don't check that in detail - rather we just check the aggregate
937
        # value.
938
        nodes = sorted(chkmap._root_node._items.items())
939
        ptr1 = nodes[0]
940
        ptr2 = nodes[1]
941
        self.assertEqual('k1', ptr1[0])
942
        self.assertEqual('k2', ptr2[0])
943
        node1 = chk_map._deserialise(chkmap._read_bytes(ptr1[1]), ptr1[1], None)
944
        self.assertIsInstance(node1, LeafNode)
945
        self.assertEqual(1, len(node1))
946
        self.assertEqual({('k1'*50,): 'v1'}, self.to_dict(node1, chkmap._store))
947
        node2 = chk_map._deserialise(chkmap._read_bytes(ptr2[1]), ptr2[1], None)
948
        self.assertIsInstance(node2, LeafNode)
949
        self.assertEqual(1, len(node2))
950
        self.assertEqual({('k2'*50,): 'v2'}, self.to_dict(node2, chkmap._store))
951
        # Having checked we have a good structure, check that the content is
952
        # still accessible.
953
        self.assertEqual(2, len(chkmap))
954
        self.assertEqual({("k1"*50,): "v1", ("k2"*50,): "v2"},
955
            self.to_dict(chkmap))
956
957
    def test_init_root_is_LeafNode_new(self):
958
        chk_bytes = self.get_chk_bytes()
959
        chkmap = CHKMap(chk_bytes, None)
960
        self.assertIsInstance(chkmap._root_node, LeafNode)
961
        self.assertEqual({}, self.to_dict(chkmap))
962
        self.assertEqual(0, len(chkmap))
963
964
    def test_init_and_save_new(self):
965
        chk_bytes = self.get_chk_bytes()
966
        chkmap = CHKMap(chk_bytes, None)
967
        key = chkmap._save()
968
        leaf_node = LeafNode()
969
        self.assertEqual([key], leaf_node.serialise(chk_bytes))
970
971
    def test_map_first_item_new(self):
972
        chk_bytes = self.get_chk_bytes()
973
        chkmap = CHKMap(chk_bytes, None)
974
        chkmap.map(("foo,",), "bar")
975
        self.assertEqual({('foo,',): 'bar'}, self.to_dict(chkmap))
976
        self.assertEqual(1, len(chkmap))
977
        key = chkmap._save()
978
        leaf_node = LeafNode()
979
        leaf_node.map(chk_bytes, ("foo,",), "bar")
980
        self.assertEqual([key], leaf_node.serialise(chk_bytes))
981
982
    def test_unmap_last_item_root_is_leaf_new(self):
983
        chkmap = self._get_map({("k1"*50,): "v1", ("k2"*50,): "v2"})
984
        chkmap.unmap(("k1"*50,))
985
        chkmap.unmap(("k2"*50,))
986
        self.assertEqual(0, len(chkmap))
987
        self.assertEqual({}, self.to_dict(chkmap))
988
        key = chkmap._save()
989
        leaf_node = LeafNode()
990
        self.assertEqual([key], leaf_node.serialise(chkmap._store))
991
992
    def test__dump_tree(self):
993
        chkmap = self._get_map({("aaa",): "value1", ("aab",): "value2",
994
                                ("bbb",): "value3",},
995
                               maximum_size=15)
996
        self.assertEqualDiff("'' InternalNode\n"
997
                             "  'a' InternalNode\n"
998
                             "    'aaa' LeafNode\n"
999
                             "      ('aaa',) 'value1'\n"
1000
                             "    'aab' LeafNode\n"
1001
                             "      ('aab',) 'value2'\n"
1002
                             "  'b' LeafNode\n"
1003
                             "      ('bbb',) 'value3'\n",
1004
                             chkmap._dump_tree())
1005
        self.assertEqualDiff("'' InternalNode\n"
1006
                             "  'a' InternalNode\n"
1007
                             "    'aaa' LeafNode\n"
1008
                             "      ('aaa',) 'value1'\n"
1009
                             "    'aab' LeafNode\n"
1010
                             "      ('aab',) 'value2'\n"
1011
                             "  'b' LeafNode\n"
1012
                             "      ('bbb',) 'value3'\n",
1013
                             chkmap._dump_tree())
1014
        self.assertEqualDiff(
1015
            "'' InternalNode sha1:0690d471eb0a624f359797d0ee4672bd68f4e236\n"
1016
            "  'a' InternalNode sha1:1514c35503da9418d8fd90c1bed553077cb53673\n"
1017
            "    'aaa' LeafNode sha1:4cc5970454d40b4ce297a7f13ddb76f63b88fefb\n"
1018
            "      ('aaa',) 'value1'\n"
1019
            "    'aab' LeafNode sha1:1d68bc90914ef8a3edbcc8bb28b00cb4fea4b5e2\n"
1020
            "      ('aab',) 'value2'\n"
1021
            "  'b' LeafNode sha1:3686831435b5596515353364eab0399dc45d49e7\n"
1022
            "      ('bbb',) 'value3'\n",
1023
            chkmap._dump_tree(include_keys=True))
1024
1025
    def test__dump_tree_in_progress(self):
1026
        chkmap = self._get_map({("aaa",): "value1", ("aab",): "value2"},
1027
                               maximum_size=10)
1028
        chkmap.map(('bbb',), 'value3')
1029
        self.assertEqualDiff("'' InternalNode\n"
1030
                             "  'a' InternalNode\n"
1031
                             "    'aaa' LeafNode\n"
1032
                             "      ('aaa',) 'value1'\n"
1033
                             "    'aab' LeafNode\n"
1034
                             "      ('aab',) 'value2'\n"
1035
                             "  'b' LeafNode\n"
1036
                             "      ('bbb',) 'value3'\n",
1037
                             chkmap._dump_tree())
1038
        # For things that are updated by adding 'bbb', we don't have a sha key
1039
        # for them yet, so they are listed as None
1040
        self.assertEqualDiff(
1041
            "'' InternalNode None\n"
1042
            "  'a' InternalNode sha1:6b0d881dd739a66f733c178b24da64395edfaafd\n"
1043
            "    'aaa' LeafNode sha1:40b39a08d895babce17b20ae5f62d187eaa4f63a\n"
1044
            "      ('aaa',) 'value1'\n"
1045
            "    'aab' LeafNode sha1:ad1dc7c4e801302c95bf1ba7b20bc45e548cd51a\n"
1046
            "      ('aab',) 'value2'\n"
1047
            "  'b' LeafNode None\n"
1048
            "      ('bbb',) 'value3'\n",
1049
            chkmap._dump_tree(include_keys=True))
1050
1051
1052
def _search_key_single(key):
1053
    """A search key function that maps all nodes to the same value"""
1054
    return 'value'
1055
1056
def _test_search_key(key):
1057
    return 'test:' + '\x00'.join(key)
1058
1059
1060
class TestMapSearchKeys(TestCaseWithStore):
1061
1062
    def test_default_chk_map_uses_flat_search_key(self):
1063
        chkmap = chk_map.CHKMap(self.get_chk_bytes(), None)
1064
        self.assertEqual('1',
1065
                         chkmap._search_key_func(('1',)))
1066
        self.assertEqual('1\x002',
1067
                         chkmap._search_key_func(('1', '2')))
1068
        self.assertEqual('1\x002\x003',
1069
                         chkmap._search_key_func(('1', '2', '3')))
1070
1071
    def test_search_key_is_passed_to_root_node(self):
1072
        chkmap = chk_map.CHKMap(self.get_chk_bytes(), None,
1073
                                search_key_func=_test_search_key)
1074
        self.assertIs(_test_search_key, chkmap._search_key_func)
1075
        self.assertEqual('test:1\x002\x003',
1076
                         chkmap._search_key_func(('1', '2', '3')))
1077
        self.assertEqual('test:1\x002\x003',
1078
                         chkmap._root_node._search_key(('1', '2', '3')))
1079
1080
    def test_search_key_passed_via__ensure_root(self):
1081
        chk_bytes = self.get_chk_bytes()
1082
        chkmap = chk_map.CHKMap(chk_bytes, None,
1083
                                search_key_func=_test_search_key)
1084
        root_key = chkmap._save()
1085
        chkmap = chk_map.CHKMap(chk_bytes, root_key,
1086
                                search_key_func=_test_search_key)
1087
        chkmap._ensure_root()
1088
        self.assertEqual('test:1\x002\x003',
1089
                         chkmap._root_node._search_key(('1', '2', '3')))
1090
1091
    def test_search_key_with_internal_node(self):
1092
        chk_bytes = self.get_chk_bytes()
1093
        chkmap = chk_map.CHKMap(chk_bytes, None,
1094
                                search_key_func=_test_search_key)
1095
        chkmap._root_node.set_maximum_size(10)
1096
        chkmap.map(('1',), 'foo')
1097
        chkmap.map(('2',), 'bar')
1098
        chkmap.map(('3',), 'baz')
1099
        self.assertEqualDiff("'' InternalNode\n"
1100
                             "  'test:1' LeafNode\n"
1101
                             "      ('1',) 'foo'\n"
1102
                             "  'test:2' LeafNode\n"
1103
                             "      ('2',) 'bar'\n"
1104
                             "  'test:3' LeafNode\n"
1105
                             "      ('3',) 'baz'\n"
1106
                             , chkmap._dump_tree())
1107
        root_key = chkmap._save()
1108
        chkmap = chk_map.CHKMap(chk_bytes, root_key,
1109
                                search_key_func=_test_search_key)
1110
        self.assertEqualDiff("'' InternalNode\n"
1111
                             "  'test:1' LeafNode\n"
1112
                             "      ('1',) 'foo'\n"
1113
                             "  'test:2' LeafNode\n"
1114
                             "      ('2',) 'bar'\n"
1115
                             "  'test:3' LeafNode\n"
1116
                             "      ('3',) 'baz'\n"
1117
                             , chkmap._dump_tree())
1118
1119
    def test_search_key_16(self):
1120
        chk_bytes = self.get_chk_bytes()
1121
        chkmap = chk_map.CHKMap(chk_bytes, None,
1122
                                search_key_func=chk_map._search_key_16)
1123
        chkmap._root_node.set_maximum_size(10)
1124
        chkmap.map(('1',), 'foo')
1125
        chkmap.map(('2',), 'bar')
1126
        chkmap.map(('3',), 'baz')
1127
        self.assertEqualDiff("'' InternalNode\n"
1128
                             "  '1' LeafNode\n"
1129
                             "      ('2',) 'bar'\n"
1130
                             "  '6' LeafNode\n"
1131
                             "      ('3',) 'baz'\n"
1132
                             "  '8' LeafNode\n"
1133
                             "      ('1',) 'foo'\n"
1134
                             , chkmap._dump_tree())
1135
        root_key = chkmap._save()
1136
        chkmap = chk_map.CHKMap(chk_bytes, root_key,
1137
                                search_key_func=chk_map._search_key_16)
1138
        # We can get the values back correctly
1139
        self.assertEqual([(('1',), 'foo')],
1140
                         list(chkmap.iteritems([('1',)])))
1141
        self.assertEqualDiff("'' InternalNode\n"
1142
                             "  '1' LeafNode\n"
1143
                             "      ('2',) 'bar'\n"
1144
                             "  '6' LeafNode\n"
1145
                             "      ('3',) 'baz'\n"
1146
                             "  '8' LeafNode\n"
1147
                             "      ('1',) 'foo'\n"
1148
                             , chkmap._dump_tree())
1149
1150
    def test_search_key_255(self):
1151
        chk_bytes = self.get_chk_bytes()
1152
        chkmap = chk_map.CHKMap(chk_bytes, None,
1153
                                search_key_func=chk_map._search_key_255)
1154
        chkmap._root_node.set_maximum_size(10)
1155
        chkmap.map(('1',), 'foo')
1156
        chkmap.map(('2',), 'bar')
1157
        chkmap.map(('3',), 'baz')
1158
        self.assertEqualDiff("'' InternalNode\n"
1159
                             "  '\\x1a' LeafNode\n"
1160
                             "      ('2',) 'bar'\n"
1161
                             "  'm' LeafNode\n"
1162
                             "      ('3',) 'baz'\n"
1163
                             "  '\\x83' LeafNode\n"
1164
                             "      ('1',) 'foo'\n"
1165
                             , chkmap._dump_tree())
1166
        root_key = chkmap._save()
1167
        chkmap = chk_map.CHKMap(chk_bytes, root_key,
1168
                                search_key_func=chk_map._search_key_255)
1169
        # We can get the values back correctly
1170
        self.assertEqual([(('1',), 'foo')],
1171
                         list(chkmap.iteritems([('1',)])))
1172
        self.assertEqualDiff("'' InternalNode\n"
1173
                             "  '\\x1a' LeafNode\n"
1174
                             "      ('2',) 'bar'\n"
1175
                             "  'm' LeafNode\n"
1176
                             "      ('3',) 'baz'\n"
1177
                             "  '\\x83' LeafNode\n"
1178
                             "      ('1',) 'foo'\n"
1179
                             , chkmap._dump_tree())
1180
1181
    def test_search_key_collisions(self):
1182
        chkmap = chk_map.CHKMap(self.get_chk_bytes(), None,
1183
                                search_key_func=_search_key_single)
1184
        # The node will want to expand, but it cannot, because it knows that
1185
        # all the keys must map to this node
1186
        chkmap._root_node.set_maximum_size(20)
1187
        chkmap.map(('1',), 'foo')
1188
        chkmap.map(('2',), 'bar')
1189
        chkmap.map(('3',), 'baz')
1190
        self.assertEqualDiff("'' LeafNode\n"
1191
                             "      ('1',) 'foo'\n"
1192
                             "      ('2',) 'bar'\n"
1193
                             "      ('3',) 'baz'\n"
1194
                             , chkmap._dump_tree())
1195
1196
1197
class TestSearchKeyFuncs(tests.TestCase):
1198
1199
    def assertSearchKey16(self, expected, key):
1200
        self.assertEqual(expected, chk_map._search_key_16(key))
1201
1202
    def assertSearchKey255(self, expected, key):
1203
        actual = chk_map._search_key_255(key)
1204
        self.assertEqual(expected, actual, 'actual: %r' % (actual,))
1205
1206
    def test_simple_16(self):
1207
        self.assertSearchKey16('8C736521', ('foo',))
1208
        self.assertSearchKey16('8C736521\x008C736521', ('foo', 'foo'))
1209
        self.assertSearchKey16('8C736521\x0076FF8CAA', ('foo', 'bar'))
1210
        self.assertSearchKey16('ED82CD11', ('abcd',))
1211
1212
    def test_simple_255(self):
1213
        self.assertSearchKey255('\x8cse!', ('foo',))
1214
        self.assertSearchKey255('\x8cse!\x00\x8cse!', ('foo', 'foo'))
1215
        self.assertSearchKey255('\x8cse!\x00v\xff\x8c\xaa', ('foo', 'bar'))
1216
        # The standard mapping for these would include '\n', so it should be
1217
        # mapped to '_'
1218
        self.assertSearchKey255('\xfdm\x93_\x00P_\x1bL', ('<', 'V'))
1219
1220
    def test_255_does_not_include_newline(self):
1221
        # When mapping via _search_key_255, we should never have the '\n'
1222
        # character, but all other 255 values should be present
1223
        chars_used = set()
1224
        for char_in in range(256):
1225
            search_key = chk_map._search_key_255((chr(char_in),))
1226
            chars_used.update(search_key)
1227
        all_chars = set([chr(x) for x in range(256)])
1228
        unused_chars = all_chars.symmetric_difference(chars_used)
1229
        self.assertEqual(set('\n'), unused_chars)
1230
1231
1232
class TestLeafNode(TestCaseWithStore):
1233
1234
    def test_current_size_empty(self):
1235
        node = LeafNode()
1236
        self.assertEqual(16, node._current_size())
1237
1238
    def test_current_size_size_changed(self):
1239
        node = LeafNode()
1240
        node.set_maximum_size(10)
1241
        self.assertEqual(17, node._current_size())
1242
1243
    def test_current_size_width_changed(self):
1244
        node = LeafNode()
1245
        node._key_width = 10
1246
        self.assertEqual(17, node._current_size())
1247
1248
    def test_current_size_items(self):
1249
        node = LeafNode()
1250
        base_size = node._current_size()
1251
        node.map(None, ("foo bar",), "baz")
1252
        self.assertEqual(base_size + 14, node._current_size())
1253
1254
    def test_deserialise_empty(self):
1255
        node = LeafNode.deserialise("chkleaf:\n10\n1\n0\n\n", ("sha1:1234",))
1256
        self.assertEqual(0, len(node))
1257
        self.assertEqual(10, node.maximum_size)
1258
        self.assertEqual(("sha1:1234",), node.key())
1259
        self.assertIs(None, node._search_prefix)
1260
        self.assertIs(None, node._common_serialised_prefix)
1261
1262
    def test_deserialise_items(self):
1263
        node = LeafNode.deserialise(
1264
            "chkleaf:\n0\n1\n2\n\nfoo bar\x001\nbaz\nquux\x001\nblarh\n",
1265
            ("sha1:1234",))
1266
        self.assertEqual(2, len(node))
1267
        self.assertEqual([(("foo bar",), "baz"), (("quux",), "blarh")],
1268
            sorted(node.iteritems(None)))
1269
1270
    def test_deserialise_item_with_null_width_1(self):
1271
        node = LeafNode.deserialise(
1272
            "chkleaf:\n0\n1\n2\n\nfoo\x001\nbar\x00baz\nquux\x001\nblarh\n",
1273
            ("sha1:1234",))
1274
        self.assertEqual(2, len(node))
1275
        self.assertEqual([(("foo",), "bar\x00baz"), (("quux",), "blarh")],
1276
            sorted(node.iteritems(None)))
1277
1278
    def test_deserialise_item_with_null_width_2(self):
1279
        node = LeafNode.deserialise(
1280
            "chkleaf:\n0\n2\n2\n\nfoo\x001\x001\nbar\x00baz\n"
1281
            "quux\x00\x001\nblarh\n",
1282
            ("sha1:1234",))
1283
        self.assertEqual(2, len(node))
1284
        self.assertEqual([(("foo", "1"), "bar\x00baz"), (("quux", ""), "blarh")],
1285
            sorted(node.iteritems(None)))
1286
1287
    def test_iteritems_selected_one_of_two_items(self):
1288
        node = LeafNode.deserialise(
1289
            "chkleaf:\n0\n1\n2\n\nfoo bar\x001\nbaz\nquux\x001\nblarh\n",
1290
            ("sha1:1234",))
1291
        self.assertEqual(2, len(node))
1292
        self.assertEqual([(("quux",), "blarh")],
1293
            sorted(node.iteritems(None, [("quux",), ("qaz",)])))
1294
1295
    def test_deserialise_item_with_common_prefix(self):
1296
        node = LeafNode.deserialise(
1297
            "chkleaf:\n0\n2\n2\nfoo\x00\n1\x001\nbar\x00baz\n2\x001\nblarh\n",
1298
            ("sha1:1234",))
1299
        self.assertEqual(2, len(node))
1300
        self.assertEqual([(("foo", "1"), "bar\x00baz"), (("foo", "2"), "blarh")],
1301
            sorted(node.iteritems(None)))
1302
        self.assertIs(chk_map._unknown, node._search_prefix)
1303
        self.assertEqual('foo\x00', node._common_serialised_prefix)
1304
1305
    def test_deserialise_multi_line(self):
1306
        node = LeafNode.deserialise(
1307
            "chkleaf:\n0\n2\n2\nfoo\x00\n1\x002\nbar\nbaz\n2\x002\nblarh\n\n",
1308
            ("sha1:1234",))
1309
        self.assertEqual(2, len(node))
1310
        self.assertEqual([(("foo", "1"), "bar\nbaz"),
1311
                          (("foo", "2"), "blarh\n"),
1312
                         ], sorted(node.iteritems(None)))
1313
        self.assertIs(chk_map._unknown, node._search_prefix)
1314
        self.assertEqual('foo\x00', node._common_serialised_prefix)
1315
1316
    def test_key_new(self):
1317
        node = LeafNode()
1318
        self.assertEqual(None, node.key())
1319
1320
    def test_key_after_map(self):
1321
        node = LeafNode.deserialise("chkleaf:\n10\n1\n0\n\n", ("sha1:1234",))
1322
        node.map(None, ("foo bar",), "baz quux")
1323
        self.assertEqual(None, node.key())
1324
1325
    def test_key_after_unmap(self):
1326
        node = LeafNode.deserialise(
1327
            "chkleaf:\n0\n1\n2\n\nfoo bar\x001\nbaz\nquux\x001\nblarh\n",
1328
            ("sha1:1234",))
1329
        node.unmap(None, ("foo bar",))
1330
        self.assertEqual(None, node.key())
1331
1332
    def test_map_exceeding_max_size_only_entry_new(self):
1333
        node = LeafNode()
1334
        node.set_maximum_size(10)
1335
        result = node.map(None, ("foo bar",), "baz quux")
1336
        self.assertEqual(("foo bar", [("", node)]), result)
1337
        self.assertTrue(10 < node._current_size())
1338
1339
    def test_map_exceeding_max_size_second_entry_early_difference_new(self):
1340
        node = LeafNode()
1341
        node.set_maximum_size(10)
1342
        node.map(None, ("foo bar",), "baz quux")
1343
        prefix, result = list(node.map(None, ("blue",), "red"))
1344
        self.assertEqual("", prefix)
1345
        self.assertEqual(2, len(result))
1346
        split_chars = set([result[0][0], result[1][0]])
1347
        self.assertEqual(set(["f", "b"]), split_chars)
1348
        nodes = dict(result)
1349
        node = nodes["f"]
1350
        self.assertEqual({("foo bar",): "baz quux"}, self.to_dict(node, None))
1351
        self.assertEqual(10, node.maximum_size)
1352
        self.assertEqual(1, node._key_width)
1353
        node = nodes["b"]
1354
        self.assertEqual({("blue",): "red"}, self.to_dict(node, None))
1355
        self.assertEqual(10, node.maximum_size)
1356
        self.assertEqual(1, node._key_width)
1357
1358
    def test_map_first(self):
1359
        node = LeafNode()
1360
        result = node.map(None, ("foo bar",), "baz quux")
1361
        self.assertEqual(("foo bar", [("", node)]), result)
1362
        self.assertEqual({("foo bar",):"baz quux"}, self.to_dict(node, None))
1363
        self.assertEqual(1, len(node))
1364
1365
    def test_map_second(self):
1366
        node = LeafNode()
1367
        node.map(None, ("foo bar",), "baz quux")
1368
        result = node.map(None, ("bingo",), "bango")
1369
        self.assertEqual(("", [("", node)]), result)
1370
        self.assertEqual({("foo bar",):"baz quux", ("bingo",):"bango"},
1371
            self.to_dict(node, None))
1372
        self.assertEqual(2, len(node))
1373
1374
    def test_map_replacement(self):
1375
        node = LeafNode()
1376
        node.map(None, ("foo bar",), "baz quux")
1377
        result = node.map(None, ("foo bar",), "bango")
1378
        self.assertEqual(("foo bar", [("", node)]), result)
1379
        self.assertEqual({("foo bar",): "bango"},
1380
            self.to_dict(node, None))
1381
        self.assertEqual(1, len(node))
1382
1383
    def test_serialise_empty(self):
1384
        store = self.get_chk_bytes()
1385
        node = LeafNode()
1386
        node.set_maximum_size(10)
1387
        expected_key = ("sha1:f34c3f0634ea3f85953dffa887620c0a5b1f4a51",)
1388
        self.assertEqual([expected_key],
1389
            list(node.serialise(store)))
1390
        self.assertEqual("chkleaf:\n10\n1\n0\n\n", self.read_bytes(store, expected_key))
1391
        self.assertEqual(expected_key, node.key())
1392
1393
    def test_serialise_items(self):
1394
        store = self.get_chk_bytes()
1395
        node = LeafNode()
1396
        node.set_maximum_size(10)
1397
        node.map(None, ("foo bar",), "baz quux")
1398
        expected_key = ("sha1:f89fac7edfc6bdb1b1b54a556012ff0c646ef5e0",)
1399
        self.assertEqual('foo bar', node._common_serialised_prefix)
1400
        self.assertEqual([expected_key],
1401
            list(node.serialise(store)))
1402
        self.assertEqual("chkleaf:\n10\n1\n1\nfoo bar\n\x001\nbaz quux\n",
1403
            self.read_bytes(store, expected_key))
1404
        self.assertEqual(expected_key, node.key())
1405
1406
    def test_unique_serialised_prefix_empty_new(self):
1407
        node = LeafNode()
1408
        self.assertIs(None, node._compute_search_prefix())
1409
1410
    def test_unique_serialised_prefix_one_item_new(self):
1411
        node = LeafNode()
1412
        node.map(None, ("foo bar", "baz"), "baz quux")
1413
        self.assertEqual("foo bar\x00baz", node._compute_search_prefix())
1414
1415
    def test_unmap_missing(self):
1416
        node = LeafNode()
1417
        self.assertRaises(KeyError, node.unmap, None, ("foo bar",))
1418
1419
    def test_unmap_present(self):
1420
        node = LeafNode()
1421
        node.map(None, ("foo bar",), "baz quux")
1422
        result = node.unmap(None, ("foo bar",))
1423
        self.assertEqual(node, result)
1424
        self.assertEqual({}, self.to_dict(node, None))
1425
        self.assertEqual(0, len(node))
1426
1427
    def test_map_maintains_common_prefixes(self):
1428
        node = LeafNode()
1429
        node._key_width = 2
1430
        node.map(None, ("foo bar", "baz"), "baz quux")
1431
        self.assertEqual('foo bar\x00baz', node._search_prefix)
1432
        self.assertEqual('foo bar\x00baz', node._common_serialised_prefix)
1433
        node.map(None, ("foo bar", "bing"), "baz quux")
1434
        self.assertEqual('foo bar\x00b', node._search_prefix)
1435
        self.assertEqual('foo bar\x00b', node._common_serialised_prefix)
1436
        node.map(None, ("fool", "baby"), "baz quux")
1437
        self.assertEqual('foo', node._search_prefix)
1438
        self.assertEqual('foo', node._common_serialised_prefix)
1439
        node.map(None, ("foo bar", "baz"), "replaced")
1440
        self.assertEqual('foo', node._search_prefix)
1441
        self.assertEqual('foo', node._common_serialised_prefix)
1442
        node.map(None, ("very", "different"), "value")
1443
        self.assertEqual('', node._search_prefix)
1444
        self.assertEqual('', node._common_serialised_prefix)
1445
1446
    def test_unmap_maintains_common_prefixes(self):
1447
        node = LeafNode()
1448
        node._key_width = 2
1449
        node.map(None, ("foo bar", "baz"), "baz quux")
1450
        node.map(None, ("foo bar", "bing"), "baz quux")
1451
        node.map(None, ("fool", "baby"), "baz quux")
1452
        node.map(None, ("very", "different"), "value")
1453
        self.assertEqual('', node._search_prefix)
1454
        self.assertEqual('', node._common_serialised_prefix)
1455
        node.unmap(None, ("very", "different"))
1456
        self.assertEqual("foo", node._search_prefix)
1457
        self.assertEqual("foo", node._common_serialised_prefix)
1458
        node.unmap(None, ("fool", "baby"))
1459
        self.assertEqual('foo bar\x00b', node._search_prefix)
1460
        self.assertEqual('foo bar\x00b', node._common_serialised_prefix)
1461
        node.unmap(None, ("foo bar", "baz"))
1462
        self.assertEqual('foo bar\x00bing', node._search_prefix)
1463
        self.assertEqual('foo bar\x00bing', node._common_serialised_prefix)
1464
        node.unmap(None, ("foo bar", "bing"))
1465
        self.assertEqual(None, node._search_prefix)
1466
        self.assertEqual(None, node._common_serialised_prefix)
1467
1468
1469
class TestInternalNode(TestCaseWithStore):
1470
1471
    def test_add_node_empty_new(self):
1472
        node = InternalNode('fo')
1473
        child = LeafNode()
1474
        child.set_maximum_size(100)
1475
        child.map(None, ("foo",), "bar")
1476
        node.add_node("foo", child)
1477
        # Note that node isn't strictly valid now as a tree (only one child),
1478
        # but thats ok for this test.
1479
        # The first child defines the node's width:
1480
        self.assertEqual(3, node._node_width)
1481
        # We should be able to iterate over the contents without doing IO.
1482
        self.assertEqual({('foo',): 'bar'}, self.to_dict(node, None))
1483
        # The length should be known:
1484
        self.assertEqual(1, len(node))
1485
        # serialising the node should serialise the child and the node.
1486
        chk_bytes = self.get_chk_bytes()
1487
        keys = list(node.serialise(chk_bytes))
1488
        child_key = child.serialise(chk_bytes)[0]
1489
        self.assertEqual(
1490
            [child_key, ('sha1:cf67e9997d8228a907c1f5bfb25a8bd9cd916fac',)],
1491
            keys)
1492
        # We should be able to access deserialised content.
1493
        bytes = self.read_bytes(chk_bytes, keys[1])
1494
        node = chk_map._deserialise(bytes, keys[1], None)
1495
        self.assertEqual(1, len(node))
1496
        self.assertEqual({('foo',): 'bar'}, self.to_dict(node, chk_bytes))
1497
        self.assertEqual(3, node._node_width)
1498
1499
    def test_add_node_resets_key_new(self):
1500
        node = InternalNode('fo')
1501
        child = LeafNode()
1502
        child.set_maximum_size(100)
1503
        child.map(None, ("foo",), "bar")
1504
        node.add_node("foo", child)
1505
        chk_bytes = self.get_chk_bytes()
1506
        keys = list(node.serialise(chk_bytes))
1507
        self.assertEqual(keys[1], node._key)
1508
        node.add_node("fos", child)
1509
        self.assertEqual(None, node._key)
1510
1511
#    def test_add_node_empty_oversized_one_ok_new(self):
1512
#    def test_add_node_one_oversized_second_kept_minimum_fan(self):
1513
#    def test_add_node_two_oversized_third_kept_minimum_fan(self):
1514
#    def test_add_node_one_oversized_second_splits_errors(self):
1515
1516
    def test__iter_nodes_no_key_filter(self):
1517
        node = InternalNode('')
1518
        child = LeafNode()
1519
        child.set_maximum_size(100)
1520
        child.map(None, ("foo",), "bar")
1521
        node.add_node("f", child)
1522
        child = LeafNode()
1523
        child.set_maximum_size(100)
1524
        child.map(None, ("bar",), "baz")
1525
        node.add_node("b", child)
1526
1527
        for child, node_key_filter in node._iter_nodes(None, key_filter=None):
1528
            self.assertEqual(None, node_key_filter)
1529
1530
    def test__iter_nodes_splits_key_filter(self):
1531
        node = InternalNode('')
1532
        child = LeafNode()
1533
        child.set_maximum_size(100)
1534
        child.map(None, ("foo",), "bar")
1535
        node.add_node("f", child)
1536
        child = LeafNode()
1537
        child.set_maximum_size(100)
1538
        child.map(None, ("bar",), "baz")
1539
        node.add_node("b", child)
1540
1541
        # foo and bar both match exactly one leaf node, but 'cat' should not
1542
        # match any, and should not be placed in one.
1543
        key_filter = (('foo',), ('bar',), ('cat',))
1544
        for child, node_key_filter in node._iter_nodes(None,
1545
                                                       key_filter=key_filter):
1546
            # each child could only match one key filter, so make sure it was
1547
            # properly filtered
1548
            self.assertEqual(1, len(node_key_filter))
1549
1550
    def test__iter_nodes_with_multiple_matches(self):
1551
        node = InternalNode('')
1552
        child = LeafNode()
1553
        child.set_maximum_size(100)
1554
        child.map(None, ("foo",), "val")
1555
        child.map(None, ("fob",), "val")
1556
        node.add_node("f", child)
1557
        child = LeafNode()
1558
        child.set_maximum_size(100)
1559
        child.map(None, ("bar",), "val")
1560
        child.map(None, ("baz",), "val")
1561
        node.add_node("b", child)
1562
4413.4.2 by John Arbash Meinel
Rewrite the shortcuts.
1563
        # Note that 'ram' doesn't match anything, so it should be freely
1564
        # ignored
1565
        key_filter = (('foo',), ('fob',), ('bar',), ('baz',), ('ram',))
4241.6.1 by Ian Clatworthy
chk_map code from brisbane-core
1566
        for child, node_key_filter in node._iter_nodes(None,
1567
                                                       key_filter=key_filter):
4413.4.2 by John Arbash Meinel
Rewrite the shortcuts.
1568
            # each child could match two key filters, so make sure they were
4241.6.1 by Ian Clatworthy
chk_map code from brisbane-core
1569
            # both included.
1570
            self.assertEqual(2, len(node_key_filter))
1571
4413.4.2 by John Arbash Meinel
Rewrite the shortcuts.
1572
    def make_fo_fa_node(self):
1573
        node = InternalNode('f')
1574
        child = LeafNode()
1575
        child.set_maximum_size(100)
1576
        child.map(None, ("foo",), "val")
1577
        child.map(None, ("fob",), "val")
1578
        node.add_node('fo', child)
1579
        child = LeafNode()
1580
        child.set_maximum_size(100)
1581
        child.map(None, ("far",), "val")
1582
        child.map(None, ("faz",), "val")
1583
        node.add_node("fa", child)
1584
        return node
1585
1586
    def test__iter_nodes_single_entry(self):
1587
        node = self.make_fo_fa_node()
1588
        key_filter = [('foo',)]
1589
        nodes = list(node._iter_nodes(None, key_filter=key_filter))
1590
        self.assertEqual(1, len(nodes))
1591
        self.assertEqual(key_filter, nodes[0][1])
1592
1593
    def test__iter_nodes_single_entry_misses(self):
1594
        node = self.make_fo_fa_node()
1595
        key_filter = [('bar',)]
1596
        nodes = list(node._iter_nodes(None, key_filter=key_filter))
1597
        self.assertEqual(0, len(nodes))
1598
1599
    def test__iter_nodes_mixed_key_width(self):
1600
        node = self.make_fo_fa_node()
1601
        key_filter = [('foo', 'bar'), ('foo',), ('fo',), ('b',)]
1602
        nodes = list(node._iter_nodes(None, key_filter=key_filter))
1603
        self.assertEqual(1, len(nodes))
1604
        matches = key_filter[:]
1605
        matches.remove(('b',))
1606
        self.assertEqual(sorted(matches), sorted(nodes[0][1]))
1607
1608
    def test__iter_nodes_match_all(self):
1609
        node = self.make_fo_fa_node()
1610
        key_filter = [('foo', 'bar'), ('foo',), ('fo',), ('f',)]
1611
        nodes = list(node._iter_nodes(None, key_filter=key_filter))
1612
        self.assertEqual(2, len(nodes))
1613
1614
    def test__iter_nodes_fixed_widths_and_misses(self):
1615
        node = self.make_fo_fa_node()
1616
        # foo and faa should both match one child, baz should miss
1617
        key_filter = [('foo',), ('faa',), ('baz',)]
1618
        nodes = list(node._iter_nodes(None, key_filter=key_filter))
1619
        self.assertEqual(2, len(nodes))
1620
        for node, matches in nodes:
1621
            self.assertEqual(1, len(matches))
1622
4241.6.1 by Ian Clatworthy
chk_map code from brisbane-core
1623
    def test_iteritems_empty_new(self):
1624
        node = InternalNode()
1625
        self.assertEqual([], sorted(node.iteritems(None)))
1626
1627
    def test_iteritems_two_children(self):
1628
        node = InternalNode()
1629
        leaf1 = LeafNode()
1630
        leaf1.map(None, ('foo bar',), 'quux')
1631
        leaf2 = LeafNode()
1632
        leaf2.map(None, ('strange',), 'beast')
1633
        node.add_node("f", leaf1)
1634
        node.add_node("s", leaf2)
1635
        self.assertEqual([(('foo bar',), 'quux'), (('strange',), 'beast')],
1636
            sorted(node.iteritems(None)))
1637
1638
    def test_iteritems_two_children_partial(self):
1639
        node = InternalNode()
1640
        leaf1 = LeafNode()
1641
        leaf1.map(None, ('foo bar',), 'quux')
1642
        leaf2 = LeafNode()
1643
        leaf2.map(None, ('strange',), 'beast')
1644
        node.add_node("f", leaf1)
1645
        # This sets up a path that should not be followed - it will error if
1646
        # the code tries to.
1647
        node._items['f'] = None
1648
        node.add_node("s", leaf2)
1649
        self.assertEqual([(('strange',), 'beast')],
1650
            sorted(node.iteritems(None, [('strange',), ('weird',)])))
1651
1652
    def test_iteritems_two_children_with_hash(self):
1653
        search_key_func = chk_map.search_key_registry.get('hash-255-way')
1654
        node = InternalNode(search_key_func=search_key_func)
1655
        leaf1 = LeafNode(search_key_func=search_key_func)
1656
        leaf1.map(None, ('foo bar',), 'quux')
1657
        leaf2 = LeafNode(search_key_func=search_key_func)
1658
        leaf2.map(None, ('strange',), 'beast')
1659
        self.assertEqual('\xbeF\x014', search_key_func(('foo bar',)))
1660
        self.assertEqual('\x85\xfa\xf7K', search_key_func(('strange',)))
1661
        node.add_node("\xbe", leaf1)
1662
        # This sets up a path that should not be followed - it will error if
1663
        # the code tries to.
1664
        node._items['\xbe'] = None
1665
        node.add_node("\x85", leaf2)
1666
        self.assertEqual([(('strange',), 'beast')],
1667
            sorted(node.iteritems(None, [('strange',), ('weird',)])))
1668
1669
    def test_iteritems_partial_empty(self):
1670
        node = InternalNode()
1671
        self.assertEqual([], sorted(node.iteritems([('missing',)])))
1672
1673
    def test_map_to_new_child_new(self):
1674
        chkmap = self._get_map({('k1',):'foo', ('k2',):'bar'}, maximum_size=10)
1675
        chkmap._ensure_root()
1676
        node = chkmap._root_node
1677
        # Ensure test validity: nothing paged in below the root.
1678
        self.assertEqual(2,
1679
            len([value for value in node._items.values()
1680
                if type(value) == tuple]))
1681
        # now, mapping to k3 should add a k3 leaf
1682
        prefix, nodes = node.map(None, ('k3',), 'quux')
1683
        self.assertEqual("k", prefix)
1684
        self.assertEqual([("", node)], nodes)
1685
        # check new child details
1686
        child = node._items['k3']
1687
        self.assertIsInstance(child, LeafNode)
1688
        self.assertEqual(1, len(child))
1689
        self.assertEqual({('k3',): 'quux'}, self.to_dict(child, None))
1690
        self.assertEqual(None, child._key)
1691
        self.assertEqual(10, child.maximum_size)
1692
        self.assertEqual(1, child._key_width)
1693
        # Check overall structure:
1694
        self.assertEqual(3, len(chkmap))
1695
        self.assertEqual({('k1',): 'foo', ('k2',): 'bar', ('k3',): 'quux'},
1696
            self.to_dict(chkmap))
1697
        # serialising should only serialise the new data - k3 and the internal
1698
        # node.
1699
        keys = list(node.serialise(chkmap._store))
1700
        child_key = child.serialise(chkmap._store)[0]
1701
        self.assertEqual([child_key, keys[1]], keys)
1702
1703
    def test_map_to_child_child_splits_new(self):
1704
        chkmap = self._get_map({('k1',):'foo', ('k22',):'bar'}, maximum_size=10)
1705
        # Check for the canonical root value for this tree:
1706
        self.assertEqualDiff("'' InternalNode\n"
1707
                             "  'k1' LeafNode\n"
1708
                             "      ('k1',) 'foo'\n"
1709
                             "  'k2' LeafNode\n"
1710
                             "      ('k22',) 'bar'\n"
1711
                             , chkmap._dump_tree())
1712
        # _dump_tree pages everything in, so reload using just the root
1713
        chkmap = CHKMap(chkmap._store, chkmap._root_node)
1714
        chkmap._ensure_root()
1715
        node = chkmap._root_node
1716
        # Ensure test validity: nothing paged in below the root.
1717
        self.assertEqual(2,
1718
            len([value for value in node._items.values()
1719
                if type(value) == tuple]))
1720
        # now, mapping to k23 causes k22 ('k2' in node) to split into k22 and
1721
        # k23, which for simplicity in the current implementation generates
1722
        # a new internal node between node, and k22/k23.
1723
        prefix, nodes = node.map(chkmap._store, ('k23',), 'quux')
1724
        self.assertEqual("k", prefix)
1725
        self.assertEqual([("", node)], nodes)
1726
        # check new child details
1727
        child = node._items['k2']
1728
        self.assertIsInstance(child, InternalNode)
1729
        self.assertEqual(2, len(child))
1730
        self.assertEqual({('k22',): 'bar', ('k23',): 'quux'},
1731
            self.to_dict(child, None))
1732
        self.assertEqual(None, child._key)
1733
        self.assertEqual(10, child.maximum_size)
1734
        self.assertEqual(1, child._key_width)
1735
        self.assertEqual(3, child._node_width)
1736
        # Check overall structure:
1737
        self.assertEqual(3, len(chkmap))
1738
        self.assertEqual({('k1',): 'foo', ('k22',): 'bar', ('k23',): 'quux'},
1739
            self.to_dict(chkmap))
1740
        # serialising should only serialise the new data - although k22 hasn't
1741
        # changed because its a special corner case (splitting on with only one
1742
        # key leaves one node unaltered), in general k22 is serialised, so we
1743
        # expect k22, k23, the new internal node, and node, to be serialised.
1744
        keys = list(node.serialise(chkmap._store))
1745
        child_key = child._key
1746
        k22_key = child._items['k22']._key
1747
        k23_key = child._items['k23']._key
1748
        self.assertEqual([k22_key, k23_key, child_key, node.key()], keys)
1749
        self.assertEqualDiff("'' InternalNode\n"
1750
                             "  'k1' LeafNode\n"
1751
                             "      ('k1',) 'foo'\n"
1752
                             "  'k2' InternalNode\n"
1753
                             "    'k22' LeafNode\n"
1754
                             "      ('k22',) 'bar'\n"
1755
                             "    'k23' LeafNode\n"
1756
                             "      ('k23',) 'quux'\n"
1757
                             , chkmap._dump_tree())
1758
1759
    def test__search_prefix_filter_with_hash(self):
1760
        search_key_func = chk_map.search_key_registry.get('hash-16-way')
1761
        node = InternalNode(search_key_func=search_key_func)
1762
        node._key_width = 2
1763
        node._node_width = 4
1764
        self.assertEqual('E8B7BE43\x0071BEEFF9', search_key_func(('a', 'b')))
1765
        self.assertEqual('E8B7', node._search_prefix_filter(('a', 'b')))
1766
        self.assertEqual('E8B7', node._search_prefix_filter(('a',)))
1767
1768
    def test_unmap_k23_from_k1_k22_k23_gives_k1_k22_tree_new(self):
1769
        chkmap = self._get_map(
1770
            {('k1',):'foo', ('k22',):'bar', ('k23',): 'quux'}, maximum_size=10)
1771
        # Check we have the expected tree.
1772
        self.assertEqualDiff("'' InternalNode\n"
1773
                             "  'k1' LeafNode\n"
1774
                             "      ('k1',) 'foo'\n"
1775
                             "  'k2' InternalNode\n"
1776
                             "    'k22' LeafNode\n"
1777
                             "      ('k22',) 'bar'\n"
1778
                             "    'k23' LeafNode\n"
1779
                             "      ('k23',) 'quux'\n"
1780
                             , chkmap._dump_tree())
1781
        chkmap = CHKMap(chkmap._store, chkmap._root_node)
1782
        chkmap._ensure_root()
1783
        node = chkmap._root_node
1784
        # unmapping k23 should give us a root, with k1 and k22 as direct
1785
        # children.
1786
        result = node.unmap(chkmap._store, ('k23',))
1787
        # check the pointed-at object within node - k2 should now point at the
1788
        # k22 leaf (which has been paged in to see if we can collapse the tree)
1789
        child = node._items['k2']
1790
        self.assertIsInstance(child, LeafNode)
1791
        self.assertEqual(1, len(child))
1792
        self.assertEqual({('k22',): 'bar'},
1793
            self.to_dict(child, None))
1794
        # Check overall structure is instact:
1795
        self.assertEqual(2, len(chkmap))
1796
        self.assertEqual({('k1',): 'foo', ('k22',): 'bar'},
1797
            self.to_dict(chkmap))
1798
        # serialising should only serialise the new data - the root node.
1799
        keys = list(node.serialise(chkmap._store))
1800
        self.assertEqual([keys[-1]], keys)
1801
        chkmap = CHKMap(chkmap._store, keys[-1])
1802
        self.assertEqualDiff("'' InternalNode\n"
1803
                             "  'k1' LeafNode\n"
1804
                             "      ('k1',) 'foo'\n"
1805
                             "  'k2' LeafNode\n"
1806
                             "      ('k22',) 'bar'\n"
1807
                             , chkmap._dump_tree())
1808
1809
    def test_unmap_k1_from_k1_k22_k23_gives_k22_k23_tree_new(self):
1810
        chkmap = self._get_map(
1811
            {('k1',):'foo', ('k22',):'bar', ('k23',): 'quux'}, maximum_size=10)
1812
        self.assertEqualDiff("'' InternalNode\n"
1813
                             "  'k1' LeafNode\n"
1814
                             "      ('k1',) 'foo'\n"
1815
                             "  'k2' InternalNode\n"
1816
                             "    'k22' LeafNode\n"
1817
                             "      ('k22',) 'bar'\n"
1818
                             "    'k23' LeafNode\n"
1819
                             "      ('k23',) 'quux'\n"
1820
                             , chkmap._dump_tree())
1821
        orig_root = chkmap._root_node
1822
        chkmap = CHKMap(chkmap._store, orig_root)
1823
        chkmap._ensure_root()
1824
        node = chkmap._root_node
1825
        k2_ptr = node._items['k2']
1826
        # unmapping k1 should give us a root, with k22 and k23 as direct
1827
        # children, and should not have needed to page in the subtree.
1828
        result = node.unmap(chkmap._store, ('k1',))
1829
        self.assertEqual(k2_ptr, result)
1830
        chkmap = CHKMap(chkmap._store, orig_root)
1831
        # Unmapping at the CHKMap level should switch to the new root
1832
        chkmap.unmap(('k1',))
1833
        self.assertEqual(k2_ptr, chkmap._root_node)
1834
        self.assertEqualDiff("'' InternalNode\n"
1835
                             "  'k22' LeafNode\n"
1836
                             "      ('k22',) 'bar'\n"
1837
                             "  'k23' LeafNode\n"
1838
                             "      ('k23',) 'quux'\n"
1839
                             , chkmap._dump_tree())
1840
1841
1842
# leaf:
1843
# map -> fits - done
1844
# map -> doesn't fit - shrink from left till fits
1845
#        key data to return: the common prefix, new nodes.
1846
1847
# unmap -> how to tell if siblings can be combined.
1848
#          combing leaf nodes means expanding the prefix to the left; so gather the size of
1849
#          all the leaf nodes addressed by expanding the prefix by 1; if any adjacent node
1850
#          is an internal node, we know that that is a dense subtree - can't combine.
1851
#          otherwise as soon as the sum of serialised values exceeds the split threshold
1852
#          we know we can't combine - stop.
1853
# unmap -> key return data - space in node, common prefix length? and key count
1854
# internal:
1855
# variable length prefixes? -> later start with fixed width to get something going
1856
# map -> fits - update pointer to leaf
1857
#        return [prefix and node] - seems sound.
1858
# map -> doesn't fit - find unique prefix and shift right
1859
#        create internal nodes for all the partitions, return list of unique
1860
#        prefixes and nodes.
1861
# map -> new prefix - create a leaf
1862
# unmap -> if child key count 0, remove
1863
# unmap -> return space in node, common prefix length? (why?), key count
1864
# map:
1865
# map, if 1 node returned, use it, otherwise make an internal and populate.
1866
# map - unmap - if empty, use empty leafnode (avoids special cases in driver
1867
# code)
1868
# map inits as empty leafnode.
1869
# tools:
1870
# visualiser
1871
1872
1873
# how to handle:
1874
# AA, AB, AC, AD, BA
1875
# packed internal node - ideal:
1876
# AA, AB, AC, AD, BA
1877
# single byte fanout - A,B,   AA,AB,AC,AD,     BA
1878
# build order's:
1879
# BA
1880
# AB - split, but we want to end up with AB, BA, in one node, with
1881
# 1-4K get0
1882
1883
1884
class TestIterInterestingNodes(TestCaseWithStore):
1885
1886
    def get_chk_bytes(self):
1887
        if getattr(self, '_chk_bytes', None) is None:
1888
            self._chk_bytes = super(TestIterInterestingNodes,
1889
                                    self).get_chk_bytes()
1890
        return self._chk_bytes
1891
1892
    def get_map_key(self, a_dict):
1893
        c_map = self._get_map(a_dict, maximum_size=10,
1894
                              chk_bytes=self.get_chk_bytes())
1895
        return c_map.key()
1896
1897
    def assertIterInteresting(self, expected, interesting_keys,
1898
                              uninteresting_keys):
1899
        """Check the result of iter_interesting_nodes.
1900
1901
        :param expected: A list of (record_keys, interesting_chk_pages,
1902
                                    interesting key value pairs)
1903
        """
1904
        store = self.get_chk_bytes()
1905
        iter_nodes = chk_map.iter_interesting_nodes(store, interesting_keys,
1906
                                                    uninteresting_keys)
1907
        nodes = list(iter_nodes)
1908
        for count, (exp, act) in enumerate(izip(expected, nodes)):
1909
            exp_record, exp_items = exp
1910
            record, items = act
1911
            exp_tuple = (exp_record, sorted(exp_items))
1912
            if record is None:
1913
                act_tuple = (None, sorted(items))
1914
            else:
1915
                act_tuple = (record.key, sorted(items))
1916
            self.assertEqual(exp_tuple, act_tuple,
1917
                             'entry %d did not match expected' % count)
1918
        self.assertEqual(len(expected), len(nodes))
1919
1920
    def test_empty_to_one_keys(self):
1921
        target = self.get_map_key({('a',): 'content'})
1922
        self.assertIterInteresting(
1923
            [(target, [(('a',), 'content')]),
1924
            ], [target], [])
1925
1926
    def test_none_to_one_key(self):
1927
        basis = self.get_map_key({})
1928
        target = self.get_map_key({('a',): 'content'})
1929
        self.assertIterInteresting(
1930
            [(None, [(('a',), 'content')]),
1931
             (target, []),
1932
            ], [target], [basis])
1933
1934
    def test_one_to_none_key(self):
1935
        basis = self.get_map_key({('a',): 'content'})
1936
        target = self.get_map_key({})
1937
        self.assertIterInteresting(
1938
            [(target, [])],
1939
            [target], [basis])
1940
1941
    def test_common_pages(self):
1942
        basis = self.get_map_key({('a',): 'content',
1943
                                  ('b',): 'content',
1944
                                  ('c',): 'content',
1945
                                 })
1946
        target = self.get_map_key({('a',): 'content',
1947
                                   ('b',): 'other content',
1948
                                   ('c',): 'content',
1949
                                  })
1950
        target_map = CHKMap(self.get_chk_bytes(), target)
1951
        self.assertEqualDiff(
1952
            "'' InternalNode\n"
1953
            "  'a' LeafNode\n"
1954
            "      ('a',) 'content'\n"
1955
            "  'b' LeafNode\n"
1956
            "      ('b',) 'other content'\n"
1957
            "  'c' LeafNode\n"
1958
            "      ('c',) 'content'\n",
1959
            target_map._dump_tree())
1960
        b_key = target_map._root_node._items['b'].key()
1961
        # This should return the root node, and the node for the 'b' key
1962
        self.assertIterInteresting(
1963
            [(target, []),
1964
             (b_key, [(('b',), 'other content')])],
1965
            [target], [basis])
1966
1967
    def test_common_sub_page(self):
1968
        basis = self.get_map_key({('aaa',): 'common',
1969
                                  ('c',): 'common',
1970
                                 })
1971
        target = self.get_map_key({('aaa',): 'common',
1972
                                   ('aab',): 'new',
1973
                                   ('c',): 'common',
1974
                                  })
1975
        target_map = CHKMap(self.get_chk_bytes(), target)
1976
        self.assertEqualDiff(
1977
            "'' InternalNode\n"
1978
            "  'a' InternalNode\n"
1979
            "    'aaa' LeafNode\n"
1980
            "      ('aaa',) 'common'\n"
1981
            "    'aab' LeafNode\n"
1982
            "      ('aab',) 'new'\n"
1983
            "  'c' LeafNode\n"
1984
            "      ('c',) 'common'\n",
1985
            target_map._dump_tree())
1986
        # The key for the internal aa node
1987
        a_key = target_map._root_node._items['a'].key()
1988
        # The key for the leaf aab node
1989
        aab_key = target_map._root_node._items['a']._items['aab'].key()
1990
        self.assertIterInteresting(
1991
            [(target, []),
1992
             (a_key, []),
1993
             (aab_key, [(('aab',), 'new')])],
1994
            [target], [basis])
1995
1996
    def test_common_leaf(self):
1997
        basis = self.get_map_key({})
1998
        target1 = self.get_map_key({('aaa',): 'common'})
1999
        target2 = self.get_map_key({('aaa',): 'common',
2000
                                    ('bbb',): 'new',
2001
                                   })
2002
        target3 = self.get_map_key({('aaa',): 'common',
2003
                                    ('aac',): 'other',
2004
                                    ('bbb',): 'new',
2005
                                   })
2006
        # The LeafNode containing 'aaa': 'common' occurs at 3 different levels.
2007
        # Once as a root node, once as a second layer, and once as a third
2008
        # layer. It should only be returned one time regardless
2009
        target1_map = CHKMap(self.get_chk_bytes(), target1)
2010
        self.assertEqualDiff(
2011
            "'' LeafNode\n"
2012
            "      ('aaa',) 'common'\n",
2013
            target1_map._dump_tree())
2014
        target2_map = CHKMap(self.get_chk_bytes(), target2)
2015
        self.assertEqualDiff(
2016
            "'' InternalNode\n"
2017
            "  'a' LeafNode\n"
2018
            "      ('aaa',) 'common'\n"
2019
            "  'b' LeafNode\n"
2020
            "      ('bbb',) 'new'\n",
2021
            target2_map._dump_tree())
2022
        target3_map = CHKMap(self.get_chk_bytes(), target3)
2023
        self.assertEqualDiff(
2024
            "'' InternalNode\n"
2025
            "  'a' InternalNode\n"
2026
            "    'aaa' LeafNode\n"
2027
            "      ('aaa',) 'common'\n"
2028
            "    'aac' LeafNode\n"
2029
            "      ('aac',) 'other'\n"
2030
            "  'b' LeafNode\n"
2031
            "      ('bbb',) 'new'\n",
2032
            target3_map._dump_tree())
2033
        aaa_key = target1_map._root_node.key()
2034
        b_key = target2_map._root_node._items['b'].key()
2035
        a_key = target3_map._root_node._items['a'].key()
2036
        aac_key = target3_map._root_node._items['a']._items['aac'].key()
2037
        self.assertIterInteresting(
2038
            [(None, [(('aaa',), 'common')]),
2039
             (target1, []),
2040
             (target2, []),
2041
             (target3, []),
2042
             (b_key, [(('bbb',), 'new')]),
2043
             (a_key, []),
2044
             (aac_key, [(('aac',), 'other')]),
2045
            ], [target1, target2, target3], [basis])
2046
2047
        self.assertIterInteresting(
2048
            [(target2, []),
2049
             (target3, []),
2050
             (b_key, [(('bbb',), 'new')]),
2051
             (a_key, []),
2052
             (aac_key, [(('aac',), 'other')]),
2053
            ], [target2, target3], [target1])
2054
2055
        # This may be a case that we relax. A root node is a deep child of the
2056
        # excluded set. The cost is buffering root nodes until we have
2057
        # determined all possible exclusions. (Because a prefix of '', cannot
2058
        # be excluded.)
2059
        self.assertIterInteresting(
2060
            [], [target1], [target3])
2061
2062
    def test_multiple_maps(self):
2063
        basis1 = self.get_map_key({('aaa',): 'common',
2064
                                   ('aab',): 'basis1',
2065
                                  })
2066
        basis2 = self.get_map_key({('bbb',): 'common',
2067
                                   ('bbc',): 'basis2',
2068
                                  })
2069
        target1 = self.get_map_key({('aaa',): 'common',
2070
                                    ('aac',): 'target1',
2071
                                    ('bbb',): 'common',
2072
                                   })
2073
        target2 = self.get_map_key({('aaa',): 'common',
2074
                                    ('bba',): 'target2',
2075
                                    ('bbb',): 'common',
2076
                                   })
2077
        target1_map = CHKMap(self.get_chk_bytes(), target1)
2078
        self.assertEqualDiff(
2079
            "'' InternalNode\n"
2080
            "  'a' InternalNode\n"
2081
            "    'aaa' LeafNode\n"
2082
            "      ('aaa',) 'common'\n"
2083
            "    'aac' LeafNode\n"
2084
            "      ('aac',) 'target1'\n"
2085
            "  'b' LeafNode\n"
2086
            "      ('bbb',) 'common'\n",
2087
            target1_map._dump_tree())
2088
        # The key for the target1 internal a node
2089
        a_key = target1_map._root_node._items['a'].key()
2090
        # The key for the leaf aac node
2091
        aac_key = target1_map._root_node._items['a']._items['aac'].key()
2092
2093
        target2_map = CHKMap(self.get_chk_bytes(), target2)
2094
        self.assertEqualDiff(
2095
            "'' InternalNode\n"
2096
            "  'a' LeafNode\n"
2097
            "      ('aaa',) 'common'\n"
2098
            "  'b' InternalNode\n"
2099
            "    'bba' LeafNode\n"
2100
            "      ('bba',) 'target2'\n"
2101
            "    'bbb' LeafNode\n"
2102
            "      ('bbb',) 'common'\n",
2103
            target2_map._dump_tree())
2104
        # The key for the target2 internal bb node
2105
        b_key = target2_map._root_node._items['b'].key()
2106
        # The key for the leaf bba node
2107
        bba_key = target2_map._root_node._items['b']._items['bba'].key()
2108
        self.assertIterInteresting(
2109
            [(target1, []),
2110
             (target2, []),
2111
             (a_key, []),
2112
             (b_key, []),
2113
             (aac_key, [(('aac',), 'target1')]),
2114
             (bba_key, [(('bba',), 'target2')]),
2115
            ], [target1, target2], [basis1, basis2])