/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Tests for maps built on a CHK versionedfiles facility."""
18
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
19
from itertools import izip
20
21
from bzrlib import chk_map
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
22
from bzrlib.chk_map import (
23
    CHKMap,
24
    InternalNode,
25
    LeafNode,
26
    _deserialise,
27
    )
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
28
from bzrlib.tests import TestCaseWithTransport
29
30
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
31
class TestCaseWithStore(TestCaseWithTransport):
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
32
33
    def get_chk_bytes(self):
34
        # The eassiest way to get a CHK store is a development3 repository and
35
        # then work with the chk_bytes attribute directly.
36
        repo = self.make_repository(".", format="development3")
37
        repo.lock_write()
38
        self.addCleanup(repo.unlock)
39
        repo.start_write_group()
40
        self.addCleanup(repo.abort_write_group)
41
        return repo.chk_bytes
42
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
43
    def _get_map(self, a_dict, maximum_size=0, chk_bytes=None, key_width=1):
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
44
        if chk_bytes is None:
45
            chk_bytes = self.get_chk_bytes()
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
46
        root_key = CHKMap.from_dict(chk_bytes, a_dict,
47
            maximum_size=maximum_size, key_width=key_width)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
48
        chkmap = CHKMap(chk_bytes, root_key)
49
        return chkmap
50
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
51
    def read_bytes(self, chk_bytes, key):
52
        stream = chk_bytes.get_record_stream([key], 'unordered', True)
53
        return stream.next().get_bytes_as("fulltext")
54
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
55
    def to_dict(self, node, *args):
56
        return dict(node.iteritems(*args))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
57
58
59
class TestMap(TestCaseWithStore):
60
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
61
    def assertHasABMap(self, chk_bytes):
3735.2.24 by Robert Collins
test_chk_map tests all passing.
62
        root_key = ('sha1:f14dd34def95036bc06bb5c0ed95437d7383a04a',)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
63
        self.assertEqual(
3735.2.24 by Robert Collins
test_chk_map tests all passing.
64
            'chkleaf:\n0\n1\n1\na\x00b\n',
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
65
            self.read_bytes(chk_bytes, root_key))
3735.2.24 by Robert Collins
test_chk_map tests all passing.
66
        return root_key
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
67
68
    def assertHasEmptyMap(self, chk_bytes):
3735.2.24 by Robert Collins
test_chk_map tests all passing.
69
        root_key = ('sha1:4e6482a3a5cb2d61699971ac77befe11a0ec5779',)
70
        self.assertEqual("chkleaf:\n0\n1\n0\n", self.read_bytes(chk_bytes, root_key))
71
        return root_key
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
72
3735.11.2 by John Arbash Meinel
Add some CHKMap test helpers to make sure that maps end up in 'canonical' form.
73
    def assertMapLayoutEqual(self, map_one, map_two):
74
        """Assert that the internal structure is identical between the maps."""
75
        map_one._ensure_root()
76
        node_one_stack = [map_one._root_node]
77
        map_two._ensure_root()
78
        node_two_stack = [map_two._root_node]
79
        while node_one_stack:
80
            node_one = node_one_stack.pop()
81
            node_two = node_two_stack.pop()
82
            if node_one.__class__ != node_two.__class__:
83
                self.assertEqualDiff(map_one._dump_tree(),
84
                                     map_two._dump_tree())
85
            if isinstance(node_one, InternalNode):
86
                # Internal nodes must have identical references
87
                self.assertEqual(sorted(node_one._items.keys()),
88
                                 sorted(node_two._items.keys()))
89
                self.assertEqual(node_one._prefix, node_two._prefix)
90
                node_one_stack.extend(node_one._iter_nodes(map_one._store))
91
                node_two_stack.extend(node_two._iter_nodes(map_two._store))
92
            else:
93
                # Leaf nodes must have identical contents
94
                self.assertEqual(node_one._items, node_two._items)
95
96
    def assertCanonicalForm(self, chkmap):
97
        """Assert that the chkmap is in 'canonical' form.
98
99
        We do this by adding all of the key value pairs from scratch, both in
100
        forward order and reverse order, and assert that the final tree layout
101
        is identical.
102
        """
103
        items = list(chkmap.iteritems())
104
        map_forward = chk_map.CHKMap(None, None)
105
        map_forward._root_node.set_maximum_size(chkmap._root_node.maximum_size)
106
        for key, value in items:
107
            map_forward.map(key, value)
108
        self.assertMapLayoutEqual(map_forward, chkmap)
109
        map_reverse = chk_map.CHKMap(None, None)
110
        map_reverse._root_node.set_maximum_size(chkmap._root_node.maximum_size)
111
        for key, value in reversed(items):
112
            map_reverse.map(key, value)
113
        self.assertMapLayoutEqual(map_reverse, chkmap)
114
115
    def test_assert_map_layout_equal(self):
116
        store = self.get_chk_bytes()
117
        map_one = CHKMap(store, None)
118
        map_one._root_node.set_maximum_size(20)
119
        map_two = CHKMap(store, None)
120
        map_two._root_node.set_maximum_size(20)
121
        self.assertMapLayoutEqual(map_one, map_two)
122
        map_one.map('aaa', 'value')
123
        self.assertRaises(AssertionError,
124
            self.assertMapLayoutEqual, map_one, map_two)
125
        map_two.map('aaa', 'value')
126
        self.assertMapLayoutEqual(map_one, map_two)
127
        # Split the tree, so we ensure that internal nodes and leaf nodes are
128
        # properly checked
129
        map_one.map('aab', 'value')
130
        self.assertIsInstance(map_one._root_node, InternalNode)
131
        self.assertRaises(AssertionError,
132
            self.assertMapLayoutEqual, map_one, map_two)
133
        map_two.map('aab', 'value')
134
        self.assertMapLayoutEqual(map_one, map_two)
135
        map_one.map('aac', 'value')
136
        self.assertRaises(AssertionError,
137
            self.assertMapLayoutEqual, map_one, map_two)
138
        self.assertCanonicalForm(map_one)
139
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
140
    def test_from_dict_empty(self):
141
        chk_bytes = self.get_chk_bytes()
142
        root_key = CHKMap.from_dict(chk_bytes, {})
3735.2.24 by Robert Collins
test_chk_map tests all passing.
143
        # Check the data was saved and inserted correctly.
144
        expected_root_key = self.assertHasEmptyMap(chk_bytes)
145
        self.assertEqual(expected_root_key, root_key)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
146
147
    def test_from_dict_ab(self):
148
        chk_bytes = self.get_chk_bytes()
149
        root_key = CHKMap.from_dict(chk_bytes, {"a":"b"})
3735.2.24 by Robert Collins
test_chk_map tests all passing.
150
        # Check the data was saved and inserted correctly.
151
        expected_root_key = self.assertHasABMap(chk_bytes)
152
        self.assertEqual(expected_root_key, root_key)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
153
154
    def test_apply_empty_ab(self):
155
        # applying a delta (None, "a", "b") to an empty chkmap generates the
156
        # same map as from_dict_ab.
157
        chk_bytes = self.get_chk_bytes()
158
        root_key = CHKMap.from_dict(chk_bytes, {})
159
        chkmap = CHKMap(chk_bytes, root_key)
160
        new_root = chkmap.apply_delta([(None, "a", "b")])
3735.2.24 by Robert Collins
test_chk_map tests all passing.
161
        # Check the data was saved and inserted correctly.
162
        expected_root_key = self.assertHasABMap(chk_bytes)
163
        self.assertEqual(expected_root_key, new_root)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
164
        # The update should have left us with an in memory root node, with an
165
        # updated key.
166
        self.assertEqual(new_root, chkmap._root_node._key)
167
168
    def test_apply_ab_empty(self):
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
169
        # applying a delta ("a", None, None) to a map with 'a' in it generates
170
        # an empty map.
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
171
        chk_bytes = self.get_chk_bytes()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
172
        root_key = CHKMap.from_dict(chk_bytes, {("a",):"b"})
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
173
        chkmap = CHKMap(chk_bytes, root_key)
3735.2.24 by Robert Collins
test_chk_map tests all passing.
174
        new_root = chkmap.apply_delta([(("a",), None, None)])
175
        # Check the data was saved and inserted correctly.
176
        expected_root_key = self.assertHasEmptyMap(chk_bytes)
177
        self.assertEqual(expected_root_key, new_root)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
178
        # The update should have left us with an in memory root node, with an
179
        # updated key.
180
        self.assertEqual(new_root, chkmap._root_node._key)
181
3735.9.3 by John Arbash Meinel
Add a direct test case that shows how the map() function is not
182
    def test_apply_delta_is_deterministic(self):
183
        chk_bytes = self.get_chk_bytes()
184
        chkmap1 = CHKMap(chk_bytes, None)
185
        chkmap1._root_node.set_maximum_size(10)
186
        chkmap1.apply_delta([(None, ('aaa',), 'common'),
187
                             (None, ('bba',), 'target2'),
188
                             (None, ('bbb',), 'common')])
189
        root_key1 = chkmap1._save()
3735.11.2 by John Arbash Meinel
Add some CHKMap test helpers to make sure that maps end up in 'canonical' form.
190
        self.assertCanonicalForm(chkmap1)
191
3735.9.3 by John Arbash Meinel
Add a direct test case that shows how the map() function is not
192
        chkmap2 = CHKMap(chk_bytes, None)
193
        chkmap2._root_node.set_maximum_size(10)
194
        chkmap2.apply_delta([(None, ('bbb',), 'common'),
195
                             (None, ('bba',), 'target2'),
196
                             (None, ('aaa',), 'common')])
197
        root_key2 = chkmap2._save()
198
        self.assertEqualDiff(chkmap1._dump_tree(), chkmap2._dump_tree())
199
        self.assertEqual(root_key1, root_key2)
3735.11.2 by John Arbash Meinel
Add some CHKMap test helpers to make sure that maps end up in 'canonical' form.
200
        self.assertCanonicalForm(chkmap2)
3735.9.3 by John Arbash Meinel
Add a direct test case that shows how the map() function is not
201
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
202
    def test_stable_splitting(self):
203
        store = self.get_chk_bytes()
204
        chkmap = CHKMap(store, None)
205
        # Should fit 2 keys per LeafNode
206
        chkmap._root_node.set_maximum_size(30)
207
        chkmap.map(('aaa',), 'v')
208
        self.assertEqualDiff("'' LeafNode None\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
209
                             "      ('aaa',) 'v'\n",
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
210
                             chkmap._dump_tree())
211
        chkmap.map(('aab',), 'v')
212
        self.assertEqualDiff("'' LeafNode None\n"
213
                             "      ('aaa',) 'v'\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
214
                             "      ('aab',) 'v'\n",
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
215
                             chkmap._dump_tree())
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
216
        self.assertCanonicalForm(chkmap)
217
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
218
        # Creates a new internal node, and splits the others into leaves
219
        chkmap.map(('aac',), 'v')
220
        self.assertEqualDiff("'' InternalNode None\n"
221
                             "  'aaa' LeafNode None\n"
222
                             "      ('aaa',) 'v'\n"
223
                             "  'aab' LeafNode None\n"
224
                             "      ('aab',) 'v'\n"
225
                             "  'aac' LeafNode None\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
226
                             "      ('aac',) 'v'\n",
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
227
                             chkmap._dump_tree())
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
228
        self.assertCanonicalForm(chkmap)
229
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
230
        # Splits again, because it can't fit in the current structure
231
        chkmap.map(('bbb',), 'v')
232
        self.assertEqualDiff("'' InternalNode None\n"
233
                             "  'a' InternalNode None\n"
234
                             "    'aaa' LeafNode None\n"
235
                             "      ('aaa',) 'v'\n"
236
                             "    'aab' LeafNode None\n"
237
                             "      ('aab',) 'v'\n"
238
                             "    'aac' LeafNode None\n"
239
                             "      ('aac',) 'v'\n"
240
                             "  'b' LeafNode None\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
241
                             "      ('bbb',) 'v'\n",
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
242
                             chkmap._dump_tree())
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
243
        self.assertCanonicalForm(chkmap)
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
244
3735.11.6 by John Arbash Meinel
It seems that map()'s split code fails when the new key is simply longer.
245
    def test_map_splits_with_longer_key(self):
246
        store = self.get_chk_bytes()
247
        chkmap = CHKMap(store, None)
248
        # Should fit 1 key per LeafNode
249
        chkmap._root_node.set_maximum_size(10)
250
        chkmap.map(('aaa',), 'v')
251
        chkmap.map(('aaaa',), 'v')
252
        self.assertCanonicalForm(chkmap)
253
        self.assertIsInstance(chkmap._root_node, InternalNode)
254
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
255
    def test_deep_splitting(self):
256
        store = self.get_chk_bytes()
257
        chkmap = CHKMap(store, None)
258
        # Should fit 2 keys per LeafNode
259
        chkmap._root_node.set_maximum_size(40)
260
        chkmap.map(('aaaaaaaa',), 'v')
261
        chkmap.map(('aaaaabaa',), 'v')
262
        self.assertEqualDiff("'' LeafNode None\n"
263
                             "      ('aaaaaaaa',) 'v'\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
264
                             "      ('aaaaabaa',) 'v'\n",
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
265
                             chkmap._dump_tree())
266
        chkmap.map(('aaabaaaa',), 'v')
267
        chkmap.map(('aaababaa',), 'v')
268
        self.assertEqualDiff("'' InternalNode None\n"
269
                             "  'aaaa' LeafNode None\n"
270
                             "      ('aaaaaaaa',) 'v'\n"
271
                             "      ('aaaaabaa',) 'v'\n"
272
                             "  'aaab' LeafNode None\n"
273
                             "      ('aaabaaaa',) 'v'\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
274
                             "      ('aaababaa',) 'v'\n",
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
275
                             chkmap._dump_tree())
276
        chkmap.map(('aaabacaa',), 'v')
277
        chkmap.map(('aaabadaa',), 'v')
278
        self.assertEqualDiff("'' InternalNode None\n"
279
                             "  'aaaa' LeafNode None\n"
280
                             "      ('aaaaaaaa',) 'v'\n"
281
                             "      ('aaaaabaa',) 'v'\n"
282
                             "  'aaab' InternalNode None\n"
283
                             "    'aaabaa' LeafNode None\n"
284
                             "      ('aaabaaaa',) 'v'\n"
285
                             "    'aaabab' LeafNode None\n"
286
                             "      ('aaababaa',) 'v'\n"
287
                             "    'aaabac' LeafNode None\n"
288
                             "      ('aaabacaa',) 'v'\n"
289
                             "    'aaabad' LeafNode None\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
290
                             "      ('aaabadaa',) 'v'\n",
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
291
                             chkmap._dump_tree())
292
        chkmap.map(('aaababba',), 'v')
293
        chkmap.map(('aaababca',), 'v')
294
        self.assertEqualDiff("'' InternalNode None\n"
295
                             "  'aaaa' LeafNode None\n"
296
                             "      ('aaaaaaaa',) 'v'\n"
297
                             "      ('aaaaabaa',) 'v'\n"
298
                             "  'aaab' InternalNode None\n"
299
                             "    'aaabaa' LeafNode None\n"
300
                             "      ('aaabaaaa',) 'v'\n"
301
                             "    'aaabab' InternalNode None\n"
302
                             "      'aaababa' LeafNode None\n"
303
                             "      ('aaababaa',) 'v'\n"
304
                             "      'aaababb' LeafNode None\n"
305
                             "      ('aaababba',) 'v'\n"
306
                             "      'aaababc' LeafNode None\n"
307
                             "      ('aaababca',) 'v'\n"
308
                             "    'aaabac' LeafNode None\n"
309
                             "      ('aaabacaa',) 'v'\n"
310
                             "    'aaabad' LeafNode None\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
311
                             "      ('aaabadaa',) 'v'\n",
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
312
                             chkmap._dump_tree())
313
        # Now we add a node that should fit around an existing InternalNode,
314
        # but has a slightly different key prefix, which causes a new
315
        # InternalNode split
316
        chkmap.map(('aaabDaaa',), 'v')
317
        self.assertEqualDiff("'' InternalNode None\n"
318
                             "  'aaaa' LeafNode None\n"
319
                             "      ('aaaaaaaa',) 'v'\n"
320
                             "      ('aaaaabaa',) 'v'\n"
321
                             "  'aaab' InternalNode None\n"
322
                             "    'aaabD' LeafNode None\n"
323
                             "      ('aaabDaaa',) 'v'\n"
324
                             "    'aaaba' InternalNode None\n"
325
                             "      'aaabaa' LeafNode None\n"
326
                             "      ('aaabaaaa',) 'v'\n"
327
                             "      'aaabab' InternalNode None\n"
328
                             "        'aaababa' LeafNode None\n"
329
                             "      ('aaababaa',) 'v'\n"
330
                             "        'aaababb' LeafNode None\n"
331
                             "      ('aaababba',) 'v'\n"
332
                             "        'aaababc' LeafNode None\n"
333
                             "      ('aaababca',) 'v'\n"
334
                             "      'aaabac' LeafNode None\n"
335
                             "      ('aaabacaa',) 'v'\n"
336
                             "      'aaabad' LeafNode None\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
337
                             "      ('aaabadaa',) 'v'\n",
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
338
                             chkmap._dump_tree())
339
3735.11.11 by John Arbash Meinel
Add logic to map() so that it can also collapse when necessary.
340
    def test_map_collapses_if_size_changes(self):
341
        store = self.get_chk_bytes()
342
        chkmap = CHKMap(store, None)
343
        # Should fit 2 keys per LeafNode
344
        chkmap._root_node.set_maximum_size(30)
345
        chkmap.map(('aaa',), 'v')
346
        chkmap.map(('aab',), 'very long value that splits')
347
        self.assertEqualDiff("'' InternalNode None\n"
348
                             "  'aaa' LeafNode None\n"
349
                             "      ('aaa',) 'v'\n"
350
                             "  'aab' LeafNode None\n"
351
                             "      ('aab',) 'very long value that splits'\n",
352
                             chkmap._dump_tree())
353
        self.assertCanonicalForm(chkmap)
354
        # Now changing the value to something small should cause a rebuild
355
        chkmap.map(('aab',), 'v')
356
        self.assertEqualDiff("'' LeafNode None\n"
357
                             "      ('aaa',) 'v'\n"
358
                             "      ('aab',) 'v'\n",
359
                             chkmap._dump_tree())
360
        self.assertCanonicalForm(chkmap)
361
362
    def test_map_double_deep_collapses(self):
363
        store = self.get_chk_bytes()
364
        chkmap = CHKMap(store, None)
365
        # Should fit 3 small keys per LeafNode
366
        chkmap._root_node.set_maximum_size(40)
367
        chkmap.map(('aaa',), 'v')
368
        chkmap.map(('aab',), 'very long value that splits')
369
        chkmap.map(('abc',), 'v')
370
        self.assertEqualDiff("'' InternalNode None\n"
371
                             "  'aa' InternalNode None\n"
372
                             "    'aaa' LeafNode None\n"
373
                             "      ('aaa',) 'v'\n"
374
                             "    'aab' LeafNode None\n"
375
                             "      ('aab',) 'very long value that splits'\n"
376
                             "  'ab' LeafNode None\n"
377
                             "      ('abc',) 'v'\n",
378
                             chkmap._dump_tree())
379
        chkmap.map(('aab',), 'v')
380
        self.assertCanonicalForm(chkmap)
381
        self.assertEqualDiff("'' LeafNode None\n"
382
                             "      ('aaa',) 'v'\n"
383
                             "      ('aab',) 'v'\n"
384
                             "      ('abc',) 'v'\n",
385
                             chkmap._dump_tree())
386
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
387
    def test_stable_unmap(self):
388
        store = self.get_chk_bytes()
389
        chkmap = CHKMap(store, None)
390
        # Should fit 2 keys per LeafNode
391
        chkmap._root_node.set_maximum_size(30)
392
        chkmap.map(('aaa',), 'v')
393
        chkmap.map(('aab',), 'v')
394
        self.assertEqualDiff("'' LeafNode None\n"
395
                             "      ('aaa',) 'v'\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
396
                             "      ('aab',) 'v'\n",
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
397
                             chkmap._dump_tree())
398
        # Creates a new internal node, and splits the others into leaves
399
        chkmap.map(('aac',), 'v')
400
        self.assertEqualDiff("'' InternalNode None\n"
401
                             "  'aaa' LeafNode None\n"
402
                             "      ('aaa',) 'v'\n"
403
                             "  'aab' LeafNode None\n"
404
                             "      ('aab',) 'v'\n"
405
                             "  'aac' LeafNode None\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
406
                             "      ('aac',) 'v'\n",
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
407
                             chkmap._dump_tree())
408
        self.assertCanonicalForm(chkmap)
409
        # Now lets unmap one of the keys, and assert that we collapse the
410
        # structures.
411
        chkmap.unmap(('aac',))
412
        self.assertEqualDiff("'' LeafNode None\n"
413
                             "      ('aaa',) 'v'\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
414
                             "      ('aab',) 'v'\n",
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
415
                             chkmap._dump_tree())
416
        self.assertCanonicalForm(chkmap)
417
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
418
    def test_unmap_double_deep(self):
3735.11.4 by John Arbash Meinel
Add a test that unmap() properly chains back up multiple levels.
419
        store = self.get_chk_bytes()
420
        chkmap = CHKMap(store, None)
3735.11.5 by John Arbash Meinel
Ensure that unmap() work even when the LeafNode isn't empty.
421
        # Should fit 3 keys per LeafNode
422
        chkmap._root_node.set_maximum_size(40)
3735.11.4 by John Arbash Meinel
Add a test that unmap() properly chains back up multiple levels.
423
        chkmap.map(('aaa',), 'v')
3735.11.5 by John Arbash Meinel
Ensure that unmap() work even when the LeafNode isn't empty.
424
        chkmap.map(('aaab',), 'v')
3735.11.4 by John Arbash Meinel
Add a test that unmap() properly chains back up multiple levels.
425
        chkmap.map(('aab',), 'very long value')
426
        chkmap.map(('abc',), 'v')
427
        self.assertEqualDiff("'' InternalNode None\n"
428
                             "  'aa' InternalNode None\n"
429
                             "    'aaa' LeafNode None\n"
430
                             "      ('aaa',) 'v'\n"
3735.11.5 by John Arbash Meinel
Ensure that unmap() work even when the LeafNode isn't empty.
431
                             "      ('aaab',) 'v'\n"
3735.11.4 by John Arbash Meinel
Add a test that unmap() properly chains back up multiple levels.
432
                             "    'aab' LeafNode None\n"
433
                             "      ('aab',) 'very long value'\n"
434
                             "  'ab' LeafNode None\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
435
                             "      ('abc',) 'v'\n",
3735.11.4 by John Arbash Meinel
Add a test that unmap() properly chains back up multiple levels.
436
                             chkmap._dump_tree())
437
        # Removing the 'aab' key should cause everything to collapse back to a
438
        # single node
439
        chkmap.unmap(('aab',))
440
        self.assertEqualDiff("'' LeafNode None\n"
441
                             "      ('aaa',) 'v'\n"
3735.11.5 by John Arbash Meinel
Ensure that unmap() work even when the LeafNode isn't empty.
442
                             "      ('aaab',) 'v'\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
443
                             "      ('abc',) 'v'\n",
3735.11.5 by John Arbash Meinel
Ensure that unmap() work even when the LeafNode isn't empty.
444
                             chkmap._dump_tree())
445
446
    def test_unmap_double_deep_non_empty_leaf(self):
447
        store = self.get_chk_bytes()
448
        chkmap = CHKMap(store, None)
449
        # Should fit 3 keys per LeafNode
450
        chkmap._root_node.set_maximum_size(40)
451
        chkmap.map(('aaa',), 'v')
452
        chkmap.map(('aab',), 'long value')
453
        chkmap.map(('aabb',), 'v')
454
        chkmap.map(('abc',), 'v')
455
        self.assertEqualDiff("'' InternalNode None\n"
456
                             "  'aa' InternalNode None\n"
457
                             "    'aaa' LeafNode None\n"
458
                             "      ('aaa',) 'v'\n"
459
                             "    'aab' LeafNode None\n"
460
                             "      ('aab',) 'long value'\n"
461
                             "      ('aabb',) 'v'\n"
462
                             "  'ab' LeafNode None\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
463
                             "      ('abc',) 'v'\n",
3735.11.5 by John Arbash Meinel
Ensure that unmap() work even when the LeafNode isn't empty.
464
                             chkmap._dump_tree())
465
        # Removing the 'aab' key should cause everything to collapse back to a
466
        # single node
467
        chkmap.unmap(('aab',))
468
        self.assertEqualDiff("'' LeafNode None\n"
469
                             "      ('aaa',) 'v'\n"
470
                             "      ('aabb',) 'v'\n"
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
471
                             "      ('abc',) 'v'\n",
3735.11.4 by John Arbash Meinel
Add a test that unmap() properly chains back up multiple levels.
472
                             chkmap._dump_tree())
473
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
474
    def test_unmap_with_known_internal_node_doesnt_page(self):
475
        store = self.get_chk_bytes()
476
        chkmap = CHKMap(store, None)
477
        # Should fit 3 keys per LeafNode
478
        chkmap._root_node.set_maximum_size(30)
479
        chkmap.map(('aaa',), 'v')
480
        chkmap.map(('aab',), 'v')
481
        chkmap.map(('aac',), 'v')
482
        chkmap.map(('abc',), 'v')
483
        chkmap.map(('acd',), 'v')
484
        self.assertEqualDiff("'' InternalNode None\n"
485
                             "  'aa' InternalNode None\n"
486
                             "    'aaa' LeafNode None\n"
487
                             "      ('aaa',) 'v'\n"
488
                             "    'aab' LeafNode None\n"
489
                             "      ('aab',) 'v'\n"
490
                             "    'aac' LeafNode None\n"
491
                             "      ('aac',) 'v'\n"
492
                             "  'ab' LeafNode None\n"
493
                             "      ('abc',) 'v'\n"
494
                             "  'ac' LeafNode None\n"
495
                             "      ('acd',) 'v'\n",
496
                             chkmap._dump_tree())
497
        # Save everything to the map, and start over
498
        chkmap = CHKMap(store, chkmap._save())
499
        # Mapping an 'aa' key loads the internal node, but should not map the
500
        # 'ab' and 'ac' nodes
501
        chkmap.map(('aad',), 'v')
502
        self.assertIsInstance(chkmap._root_node._items['aa'], InternalNode)
503
        self.assertIsInstance(chkmap._root_node._items['ab'], tuple)
504
        self.assertIsInstance(chkmap._root_node._items['ac'], tuple)
505
        # Unmapping 'acd' can notice that 'aa' is an InternalNode and not have
506
        # to map in 'ab'
507
        chkmap.unmap(('acd',))
508
        self.assertIsInstance(chkmap._root_node._items['aa'], InternalNode)
509
        self.assertIsInstance(chkmap._root_node._items['ab'], tuple)
510
511
    def test_unmap_without_fitting_doesnt_page_in(self):
512
        store = self.get_chk_bytes()
513
        chkmap = CHKMap(store, None)
514
        # Should fit 2 keys per LeafNode
515
        chkmap._root_node.set_maximum_size(20)
516
        chkmap.map(('aaa',), 'v')
517
        chkmap.map(('aab',), 'v')
518
        self.assertEqualDiff("'' InternalNode None\n"
519
                             "  'aaa' LeafNode None\n"
520
                             "      ('aaa',) 'v'\n"
521
                             "  'aab' LeafNode None\n"
522
                             "      ('aab',) 'v'\n",
523
                             chkmap._dump_tree())
524
        # Save everything to the map, and start over
525
        chkmap = CHKMap(store, chkmap._save())
526
        chkmap.map(('aac',), 'v')
527
        chkmap.map(('aad',), 'v')
528
        chkmap.map(('aae',), 'v')
529
        chkmap.map(('aaf',), 'v')
530
        # At this point, the previous nodes should not be paged in, but the
531
        # newly added nodes would be
532
        self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)
533
        self.assertIsInstance(chkmap._root_node._items['aab'], tuple)
534
        self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)
535
        self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
536
        self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)
537
        self.assertIsInstance(chkmap._root_node._items['aaf'], LeafNode)
538
        # Now unmapping one of the new nodes will use only the already-paged-in
539
        # nodes to determine that we don't need to do more.
540
        chkmap.unmap(('aaf',))
541
        self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)
542
        self.assertIsInstance(chkmap._root_node._items['aab'], tuple)
543
        self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)
544
        self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
545
        self.assertIsInstance(chkmap._root_node._items['aae'], LeafNode)
546
547
    def test_unmap_pages_in_if_necessary(self):
548
        store = self.get_chk_bytes()
549
        chkmap = CHKMap(store, None)
550
        # Should fit 2 keys per LeafNode
551
        chkmap._root_node.set_maximum_size(20)
552
        chkmap.map(('aaa',), 'v')
553
        chkmap.map(('aab',), 'v')
554
        chkmap.map(('aac',), 'v')
555
        self.assertEqualDiff("'' InternalNode None\n"
556
                             "  'aaa' LeafNode None\n"
557
                             "      ('aaa',) 'v'\n"
558
                             "  'aab' LeafNode None\n"
559
                             "      ('aab',) 'v'\n"
560
                             "  'aac' LeafNode None\n"
561
                             "      ('aac',) 'v'\n",
562
                             chkmap._dump_tree())
563
        # Save everything to the map, and start over
564
        chkmap = CHKMap(store, chkmap._save())
565
        chkmap.map(('aad',), 'v')
566
        # At this point, the previous nodes should not be paged in, but the
567
        # newly added node would be
568
        self.assertIsInstance(chkmap._root_node._items['aaa'], tuple)
569
        self.assertIsInstance(chkmap._root_node._items['aab'], tuple)
570
        self.assertIsInstance(chkmap._root_node._items['aac'], tuple)
571
        self.assertIsInstance(chkmap._root_node._items['aad'], LeafNode)
572
        # Unmapping the new node will check the existing nodes to see if they
573
        # would fit, and find out that they do not
574
        chkmap.unmap(('aad',))
575
        self.assertIsInstance(chkmap._root_node._items['aaa'], LeafNode)
576
        self.assertIsInstance(chkmap._root_node._items['aab'], LeafNode)
577
        self.assertIsInstance(chkmap._root_node._items['aac'], LeafNode)
578
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
579
    def test_iter_changes_empty_ab(self):
580
        # Asking for changes between an empty dict to a dict with keys returns
581
        # all the keys.
3735.2.31 by Robert Collins
CHKMap.iter_changes
582
        basis = self._get_map({}, maximum_size=10)
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
583
        target = self._get_map(
584
            {('a',): 'content here', ('b',): 'more content'},
3735.2.31 by Robert Collins
CHKMap.iter_changes
585
            chk_bytes=basis._store, maximum_size=10)
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
586
        self.assertEqual([(('a',), None, 'content here'),
3735.2.31 by Robert Collins
CHKMap.iter_changes
587
            (('b',), None, 'more content')],
588
            sorted(list(target.iter_changes(basis))))
589
590
    def test_iter_changes_ab_empty(self):
591
        # Asking for changes between a dict with keys to an empty dict returns
592
        # all the keys.
593
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
594
            maximum_size=10)
595
        target = self._get_map({}, chk_bytes=basis._store, maximum_size=10)
596
        self.assertEqual([(('a',), 'content here', None),
597
            (('b',), 'more content', None)],
598
            sorted(list(target.iter_changes(basis))))
599
600
    def test_iter_changes_empty_empty_is_empty(self):
601
        basis = self._get_map({}, maximum_size=10)
602
        target = self._get_map({}, chk_bytes=basis._store, maximum_size=10)
603
        self.assertEqual([], sorted(list(target.iter_changes(basis))))
604
605
    def test_iter_changes_ab_ab_is_empty(self):
606
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
607
            maximum_size=10)
608
        target = self._get_map(
609
            {('a',): 'content here', ('b',): 'more content'},
610
            chk_bytes=basis._store, maximum_size=10)
611
        self.assertEqual([], sorted(list(target.iter_changes(basis))))
612
613
    def test_iter_changes_ab_ab_nodes_not_loaded(self):
614
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
615
            maximum_size=10)
616
        target = self._get_map(
617
            {('a',): 'content here', ('b',): 'more content'},
618
            chk_bytes=basis._store, maximum_size=10)
619
        list(target.iter_changes(basis))
620
        self.assertIsInstance(target._root_node, tuple)
621
        self.assertIsInstance(basis._root_node, tuple)
622
623
    def test_iter_changes_ab_ab_changed_values_shown(self):
624
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
625
            maximum_size=10)
626
        target = self._get_map(
627
            {('a',): 'content here', ('b',): 'different content'},
628
            chk_bytes=basis._store, maximum_size=10)
629
        result = sorted(list(target.iter_changes(basis)))
630
        self.assertEqual([(('b',), 'more content', 'different content')],
631
            result)
632
633
    def test_iter_changes_mixed_node_length(self):
634
        # When one side has different node lengths than the other, common
635
        # but different keys still need to be show, and new-and-old included
636
        # appropriately.
637
        # aaa - common unaltered
638
        # aab - common altered
639
        # b - basis only
640
        # at - target only
641
        # we expect: 
642
        # aaa to be not loaded (later test)
643
        # aab, b, at to be returned.
644
        # basis splits at byte 0,1,2, aaa is commonb is basis only
645
        basis_dict = {('aaa',): 'foo bar',
646
            ('aab',): 'common altered a', ('b',): 'foo bar b'}
647
        # target splits at byte 1,2, at is target only
648
        target_dict = {('aaa',): 'foo bar',
649
            ('aab',): 'common altered b', ('at',): 'foo bar t'}
650
        changes = [
651
            (('aab',), 'common altered a', 'common altered b'),
652
            (('at',), None, 'foo bar t'),
653
            (('b',), 'foo bar b', None),
654
            ]
655
        basis = self._get_map(basis_dict, maximum_size=10)
656
        target = self._get_map(target_dict, maximum_size=10,
657
            chk_bytes=basis._store)
658
        self.assertEqual(changes, sorted(list(target.iter_changes(basis))))
659
660
    def test_iter_changes_common_pages_not_loaded(self):
661
        # aaa - common unaltered
662
        # aab - common altered
663
        # b - basis only
664
        # at - target only
665
        # we expect: 
666
        # aaa to be not loaded
667
        # aaa not to be in result.
668
        basis_dict = {('aaa',): 'foo bar',
669
            ('aab',): 'common altered a', ('b',): 'foo bar b'}
670
        # target splits at byte 1, at is target only
671
        target_dict = {('aaa',): 'foo bar',
672
            ('aab',): 'common altered b', ('at',): 'foo bar t'}
673
        basis = self._get_map(basis_dict, maximum_size=10)
674
        target = self._get_map(target_dict, maximum_size=10,
675
            chk_bytes=basis._store)
3735.2.32 by Robert Collins
Activate test for common node skipping. - 50 times performance improvement.
676
        basis_get = basis._store.get_record_stream
677
        def get_record_stream(keys, order, fulltext):
678
            if ('sha1:1adf7c0d1b9140ab5f33bb64c6275fa78b1580b7',) in keys:
679
                self.fail("'aaa' pointer was followed %r" % keys)
680
            return basis_get(keys, order, fulltext)
681
        basis._store.get_record_stream = get_record_stream
3735.2.31 by Robert Collins
CHKMap.iter_changes
682
        result = sorted(list(target.iter_changes(basis)))
683
        for change in result:
684
            if change[0] == ('aaa',):
685
                self.fail("Found unexpected change: %s" % change)
686
687
    def test_iter_changes_unchanged_keys_in_multi_key_leafs_ignored(self):
688
        # Within a leaf there are no hash's to exclude keys, make sure multi
689
        # value leaf nodes are handled well.
690
        basis_dict = {('aaa',): 'foo bar',
691
            ('aab',): 'common altered a', ('b',): 'foo bar b'}
692
        target_dict = {('aaa',): 'foo bar',
693
            ('aab',): 'common altered b', ('at',): 'foo bar t'}
694
        changes = [
695
            (('aab',), 'common altered a', 'common altered b'),
696
            (('at',), None, 'foo bar t'),
697
            (('b',), 'foo bar b', None),
698
            ]
699
        basis = self._get_map(basis_dict)
700
        target = self._get_map(target_dict, chk_bytes=basis._store)
701
        self.assertEqual(changes, sorted(list(target.iter_changes(basis))))
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
702
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
703
    def test_iteritems_empty(self):
704
        chk_bytes = self.get_chk_bytes()
705
        root_key = CHKMap.from_dict(chk_bytes, {})
706
        chkmap = CHKMap(chk_bytes, root_key)
707
        self.assertEqual([], list(chkmap.iteritems()))
708
709
    def test_iteritems_two_items(self):
710
        chk_bytes = self.get_chk_bytes()
711
        root_key = CHKMap.from_dict(chk_bytes,
712
            {"a":"content here", "b":"more content"})
713
        chkmap = CHKMap(chk_bytes, root_key)
3735.2.24 by Robert Collins
test_chk_map tests all passing.
714
        self.assertEqual([(("a",), "content here"), (("b",), "more content")],
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
715
            sorted(list(chkmap.iteritems())))
716
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
717
    def test_iteritems_selected_one_of_two_items(self):
3735.2.24 by Robert Collins
test_chk_map tests all passing.
718
        chkmap = self._get_map( {("a",):"content here", ("b",):"more content"})
719
        self.assertEqual({("a",): "content here"},
720
            self.to_dict(chkmap, [("a",)]))
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
721
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
722
    def test_iteritems_keys_prefixed_by_2_width_nodes(self):
723
        chkmap = self._get_map(
724
            {("a","a"):"content here", ("a", "b",):"more content",
725
             ("b", ""): 'boring content'},
726
            maximum_size=10, key_width=2)
727
        self.assertEqual(
728
            {("a", "a"): "content here", ("a", "b"): 'more content'},
729
            self.to_dict(chkmap, [("a",)]))
730
731
    def test_iteritems_keys_prefixed_by_2_width_one_leaf(self):
732
        chkmap = self._get_map(
733
            {("a","a"):"content here", ("a", "b",):"more content",
734
             ("b", ""): 'boring content'}, key_width=2)
735
        self.assertEqual(
736
            {("a", "a"): "content here", ("a", "b"): 'more content'},
737
            self.to_dict(chkmap, [("a",)]))
738
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
739
    def test___len__empty(self):
740
        chkmap = self._get_map({})
741
        self.assertEqual(0, len(chkmap))
742
743
    def test___len__2(self):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
744
        chkmap = self._get_map({"foo":"bar", "gam":"quux"})
745
        self.assertEqual(2, len(chkmap))
746
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
747
    def test_max_size_100_bytes_new(self):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
748
        # When there is a 100 byte upper node limit, a tree is formed.
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
749
        chkmap = self._get_map({("k1"*50,):"v1", ("k2"*50,):"v2"}, maximum_size=100)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
750
        # We expect three nodes:
751
        # A root, with two children, and with two key prefixes - k1 to one, and
752
        # k2 to the other as our node splitting is only just being developed.
753
        # The maximum size should be embedded
754
        chkmap._ensure_root()
755
        self.assertEqual(100, chkmap._root_node.maximum_size)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
756
        self.assertEqual(1, chkmap._root_node._key_width)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
757
        # There should be two child nodes, and prefix of 2(bytes):
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
758
        self.assertEqual(2, len(chkmap._root_node._items))
759
        self.assertEqual("k", chkmap._root_node.unique_serialised_prefix())
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
760
        # The actual nodes pointed at will change as serialisers change; so
761
        # here we test that the key prefix is correct; then load the nodes and
762
        # check they have the right pointed at key; whether they have the
763
        # pointed at value inline or not is also unrelated to this test so we
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
764
        # don't check that in detail - rather we just check the aggregate
765
        # value.
766
        nodes = sorted(chkmap._root_node._items.items())
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
767
        ptr1 = nodes[0]
768
        ptr2 = nodes[1]
769
        self.assertEqual('k1', ptr1[0])
770
        self.assertEqual('k2', ptr2[0])
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
771
        node1 = _deserialise(chkmap._read_bytes(ptr1[1]), ptr1[1])
772
        self.assertIsInstance(node1, LeafNode)
773
        self.assertEqual(1, len(node1))
774
        self.assertEqual({('k1'*50,): 'v1'}, self.to_dict(node1, chkmap._store))
775
        node2 = _deserialise(chkmap._read_bytes(ptr2[1]), ptr2[1])
776
        self.assertIsInstance(node2, LeafNode)
777
        self.assertEqual(1, len(node2))
778
        self.assertEqual({('k2'*50,): 'v2'}, self.to_dict(node2, chkmap._store))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
779
        # Having checked we have a good structure, check that the content is
780
        # still accessible.
781
        self.assertEqual(2, len(chkmap))
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
782
        self.assertEqual({("k1"*50,): "v1", ("k2"*50,): "v2"},
783
            self.to_dict(chkmap))
784
785
    def test_init_root_is_LeafNode_new(self):
786
        chk_bytes = self.get_chk_bytes()
787
        chkmap = CHKMap(chk_bytes, None)
788
        self.assertIsInstance(chkmap._root_node, LeafNode)
789
        self.assertEqual({}, self.to_dict(chkmap))
790
        self.assertEqual(0, len(chkmap))
791
792
    def test_init_and_save_new(self):
793
        chk_bytes = self.get_chk_bytes()
794
        chkmap = CHKMap(chk_bytes, None)
795
        key = chkmap._save()
796
        leaf_node = LeafNode()
797
        self.assertEqual([key], leaf_node.serialise(chk_bytes))
798
799
    def test_map_first_item_new(self):
800
        chk_bytes = self.get_chk_bytes()
801
        chkmap = CHKMap(chk_bytes, None)
802
        chkmap.map(("foo,",), "bar")
803
        self.assertEqual({('foo,',): 'bar'}, self.to_dict(chkmap))
804
        self.assertEqual(1, len(chkmap))
805
        key = chkmap._save()
806
        leaf_node = LeafNode()
807
        leaf_node.map(chk_bytes, ("foo,",), "bar")
808
        self.assertEqual([key], leaf_node.serialise(chk_bytes))
809
810
    def test_unmap_last_item_root_is_leaf_new(self):
811
        chkmap = self._get_map({("k1"*50,): "v1", ("k2"*50,): "v2"})
812
        chkmap.unmap(("k1"*50,))
813
        chkmap.unmap(("k2"*50,))
814
        self.assertEqual(0, len(chkmap))
815
        self.assertEqual({}, self.to_dict(chkmap))
816
        key = chkmap._save()
817
        leaf_node = LeafNode()
818
        self.assertEqual([key], leaf_node.serialise(chkmap._store))
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
819
3735.9.2 by John Arbash Meinel
Add a _dump_tree helper that assists in debugging what is going on.
820
    def test__dump_tree(self):
821
        chkmap = self._get_map({("aaa",): "value1", ("aab",): "value2",
822
                                ("bbb",): "value3",},
823
                               maximum_size=10)
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
824
        self.assertEqualDiff('\n'.join([
825
            "'' InternalNode sha1:cd9b68f18c9754a79065b06379fba543f9031742",
826
            "  'a' InternalNode sha1:ed0ceb5aeb87c56df007a17997134328ff4d0b8d",
827
            "    'aaa' LeafNode sha1:16fa5a38b80d29b529afc45f7a4f894650fc067f",
828
            "      ('aaa',) 'value1'",
829
            "    'aab' LeafNode sha1:8fca5400dc99ef1b464e60ca25da53b57406ed38",
830
            "      ('aab',) 'value2'",
831
            "  'b' LeafNode sha1:67f15d1dfa451d388ed08ff17b4f9578ba010d01",
832
            "      ('bbb',) 'value3'",
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
833
            ""]), chkmap._dump_tree())
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
834
835
    def test__dump_tree_in_progress(self):
836
        chkmap = self._get_map({("aaa",): "value1", ("aab",): "value2"},
837
                               maximum_size=10)
838
        chkmap.map(('bbb',), 'value3')
839
        # XXX: Note that this representation is different than the one for
840
        #      test__dump_tree, even though they have the same values
841
        self.assertEqualDiff('\n'.join([
842
            "'' InternalNode None",
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
843
            "  'a' InternalNode sha1:ed0ceb5aeb87c56df007a17997134328ff4d0b8d",
844
            "    'aaa' LeafNode sha1:16fa5a38b80d29b529afc45f7a4f894650fc067f",
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
845
            "      ('aaa',) 'value1'",
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
846
            "    'aab' LeafNode sha1:8fca5400dc99ef1b464e60ca25da53b57406ed38",
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
847
            "      ('aab',) 'value2'",
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
848
            "  'b' LeafNode None",
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
849
            "      ('bbb',) 'value3'",
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
850
            ""]), chkmap._dump_tree())
3735.9.2 by John Arbash Meinel
Add a _dump_tree helper that assists in debugging what is going on.
851
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
852
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
853
class TestLeafNode(TestCaseWithStore):
854
855
    def test_current_size_empty(self):
856
        node = LeafNode()
857
        self.assertEqual(15, node._current_size())
858
859
    def test_current_size_size_changed(self):
860
        node = LeafNode()
861
        node.set_maximum_size(10)
862
        self.assertEqual(16, node._current_size())
863
864
    def test_current_size_width_changed(self):
865
        node = LeafNode()
866
        node._key_width = 10
867
        self.assertEqual(16, node._current_size())
868
869
    def test_current_size_items(self):
870
        node = LeafNode()
871
        base_size = node._current_size()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
872
        node.map(None, ("foo bar",), "baz")
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
873
        self.assertEqual(base_size + 12, node._current_size())
874
875
    def test_deserialise_empty(self):
876
        node = LeafNode.deserialise("chkleaf:\n10\n1\n0\n", ("sha1:1234",))
877
        self.assertEqual(0, len(node))
878
        self.assertEqual(10, node.maximum_size)
879
        self.assertEqual(("sha1:1234",), node.key())
880
881
    def test_deserialise_items(self):
882
        node = LeafNode.deserialise(
883
            "chkleaf:\n0\n1\n2\nfoo bar\x00baz\nquux\x00blarh\n", ("sha1:1234",))
884
        self.assertEqual(2, len(node))
885
        self.assertEqual([(("foo bar",), "baz"), (("quux",), "blarh")],
3735.2.24 by Robert Collins
test_chk_map tests all passing.
886
            sorted(node.iteritems(None)))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
887
3735.2.25 by Robert Collins
CHKInventory core tests passing.
888
    def test_deserialise_item_with_null_width_1(self):
889
        node = LeafNode.deserialise(
890
            "chkleaf:\n0\n1\n2\nfoo\x00bar\x00baz\nquux\x00blarh\n",
891
            ("sha1:1234",))
892
        self.assertEqual(2, len(node))
893
        self.assertEqual([(("foo",), "bar\x00baz"), (("quux",), "blarh")],
894
            sorted(node.iteritems(None)))
895
896
    def test_deserialise_item_with_null_width_2(self):
897
        node = LeafNode.deserialise(
898
            "chkleaf:\n0\n2\n2\nfoo\x001\x00bar\x00baz\nquux\x00\x00blarh\n",
899
            ("sha1:1234",))
900
        self.assertEqual(2, len(node))
901
        self.assertEqual([(("foo", "1"), "bar\x00baz"), (("quux", ""), "blarh")],
902
            sorted(node.iteritems(None)))
903
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
904
    def test_iteritems_selected_one_of_two_items(self):
905
        node = LeafNode.deserialise(
906
            "chkleaf:\n0\n1\n2\nfoo bar\x00baz\nquux\x00blarh\n", ("sha1:1234",))
907
        self.assertEqual(2, len(node))
908
        self.assertEqual([(("quux",), "blarh")],
3735.2.24 by Robert Collins
test_chk_map tests all passing.
909
            sorted(node.iteritems(None, [("quux",), ("qaz",)])))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
910
911
    def test_key_new(self):
912
        node = LeafNode()
913
        self.assertEqual(None, node.key())
914
915
    def test_key_after_map(self):
916
        node = LeafNode.deserialise("chkleaf:\n10\n1\n0\n", ("sha1:1234",))
3735.2.24 by Robert Collins
test_chk_map tests all passing.
917
        node.map(None, ("foo bar",), "baz quux")
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
918
        self.assertEqual(None, node.key())
919
920
    def test_key_after_unmap(self):
921
        node = LeafNode.deserialise(
922
            "chkleaf:\n0\n1\n2\nfoo bar\x00baz\nquux\x00blarh\n", ("sha1:1234",))
3735.2.24 by Robert Collins
test_chk_map tests all passing.
923
        node.unmap(None, ("foo bar",))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
924
        self.assertEqual(None, node.key())
925
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
926
    def test_map_exceeding_max_size_only_entry_new(self):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
927
        node = LeafNode()
928
        node.set_maximum_size(10)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
929
        result = node.map(None, ("foo bar",), "baz quux")
930
        self.assertEqual(("foo bar", [("", node)]), result)
931
        self.assertTrue(10 < node._current_size())
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
932
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
933
    def test_map_exceeding_max_size_second_entry_early_difference_new(self):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
934
        node = LeafNode()
935
        node.set_maximum_size(10)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
936
        node.map(None, ("foo bar",), "baz quux")
937
        prefix, result = list(node.map(None, ("blue",), "red"))
938
        self.assertEqual("", prefix)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
939
        self.assertEqual(2, len(result))
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
940
        split_chars = set([result[0][0], result[1][0]])
941
        self.assertEqual(set(["f", "b"]), split_chars)
942
        nodes = dict(result)
943
        node = nodes["f"]
944
        self.assertEqual({("foo bar",): "baz quux"}, self.to_dict(node, None))
945
        self.assertEqual(10, node.maximum_size)
946
        self.assertEqual(1, node._key_width)
947
        node = nodes["b"]
948
        self.assertEqual({("blue",): "red"}, self.to_dict(node, None))
949
        self.assertEqual(10, node.maximum_size)
950
        self.assertEqual(1, node._key_width)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
951
952
    def test_map_first(self):
953
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
954
        result = node.map(None, ("foo bar",), "baz quux")
955
        self.assertEqual(("foo bar", [("", node)]), result)
956
        self.assertEqual({("foo bar",):"baz quux"}, self.to_dict(node, None))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
957
        self.assertEqual(1, len(node))
958
959
    def test_map_second(self):
960
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
961
        node.map(None, ("foo bar",), "baz quux")
962
        result = node.map(None, ("bingo",), "bango")
963
        self.assertEqual(("", [("", node)]), result)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
964
        self.assertEqual({("foo bar",):"baz quux", ("bingo",):"bango"},
3735.2.24 by Robert Collins
test_chk_map tests all passing.
965
            self.to_dict(node, None))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
966
        self.assertEqual(2, len(node))
967
968
    def test_map_replacement(self):
969
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
970
        node.map(None, ("foo bar",), "baz quux")
971
        result = node.map(None, ("foo bar",), "bango")
972
        self.assertEqual(("foo bar", [("", node)]), result)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
973
        self.assertEqual({("foo bar",): "bango"},
3735.2.24 by Robert Collins
test_chk_map tests all passing.
974
            self.to_dict(node, None))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
975
        self.assertEqual(1, len(node))
976
977
    def test_serialise_empty(self):
978
        store = self.get_chk_bytes()
979
        node = LeafNode()
980
        node.set_maximum_size(10)
981
        expected_key = ("sha1:62cc3565b48b0e830216e652cf99c6bd6b05b4b9",)
982
        self.assertEqual([expected_key],
983
            list(node.serialise(store)))
984
        self.assertEqual("chkleaf:\n10\n1\n0\n", self.read_bytes(store, expected_key))
985
        self.assertEqual(expected_key, node.key())
986
987
    def test_serialise_items(self):
988
        store = self.get_chk_bytes()
989
        node = LeafNode()
990
        node.set_maximum_size(10)
3735.2.24 by Robert Collins
test_chk_map tests all passing.
991
        node.map(None, ("foo bar",), "baz quux")
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
992
        expected_key = ("sha1:d44cb6f0299b7e047da7f9e98f810e98f1dce1a7",)
993
        self.assertEqual([expected_key],
994
            list(node.serialise(store)))
995
        self.assertEqual("chkleaf:\n10\n1\n1\nfoo bar\x00baz quux\n",
996
            self.read_bytes(store, expected_key))
997
        self.assertEqual(expected_key, node.key())
998
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
999
    def test_unique_serialised_prefix_empty_new(self):
1000
        node = LeafNode()
1001
        self.assertEqual("", node.unique_serialised_prefix())
1002
1003
    def test_unique_serialised_prefix_one_item_new(self):
1004
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
1005
        node.map(None, ("foo bar", "baz"), "baz quux")
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1006
        self.assertEqual("foo bar\x00baz", node.unique_serialised_prefix())
1007
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1008
    def test_unmap_missing(self):
1009
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
1010
        self.assertRaises(KeyError, node.unmap, None, ("foo bar",))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1011
1012
    def test_unmap_present(self):
1013
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
1014
        node.map(None, ("foo bar",), "baz quux")
1015
        result = node.unmap(None, ("foo bar",))
1016
        self.assertEqual(node, result)
1017
        self.assertEqual({}, self.to_dict(node, None))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1018
        self.assertEqual(0, len(node))
1019
1020
1021
class TestInternalNode(TestCaseWithStore):
1022
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1023
    def test_add_node_empty_new(self):
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
1024
        node = InternalNode('fo')
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1025
        child = LeafNode()
1026
        child.set_maximum_size(100)
1027
        child.map(None, ("foo",), "bar")
1028
        node.add_node("foo", child)
1029
        # Note that node isn't strictly valid now as a tree (only one child),
1030
        # but thats ok for this test.
1031
        # The first child defines the node's width:
1032
        self.assertEqual(3, node._node_width)
1033
        # We should be able to iterate over the contents without doing IO.
1034
        self.assertEqual({('foo',): 'bar'}, self.to_dict(node, None))
1035
        # The length should be known:
1036
        self.assertEqual(1, len(node))
1037
        # serialising the node should serialise the child and the node.
1038
        chk_bytes = self.get_chk_bytes()
1039
        keys = list(node.serialise(chk_bytes))
1040
        child_key = child.serialise(chk_bytes)[0]
1041
        self.assertEqual(
1042
            [child_key, ('sha1:db23b260c2bf46bf7446c39f91668900a2491610',)],
1043
            keys)
1044
        # We should be able to access deserialised content.
1045
        bytes = self.read_bytes(chk_bytes, keys[1])
1046
        node = _deserialise(bytes, keys[1])
1047
        self.assertEqual(1, len(node))
1048
        self.assertEqual({('foo',): 'bar'}, self.to_dict(node, chk_bytes))
1049
        self.assertEqual(3, node._node_width)
1050
1051
    def test_add_node_resets_key_new(self):
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
1052
        node = InternalNode('fo')
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1053
        child = LeafNode()
1054
        child.set_maximum_size(100)
1055
        child.map(None, ("foo",), "bar")
1056
        node.add_node("foo", child)
1057
        chk_bytes = self.get_chk_bytes()
1058
        keys = list(node.serialise(chk_bytes))
1059
        self.assertEqual(keys[1], node._key)
1060
        node.add_node("fos", child)
1061
        self.assertEqual(None, node._key)
1062
1063
#    def test_add_node_empty_oversized_one_ok_new(self):
1064
#    def test_add_node_one_oversized_second_kept_minimum_fan(self):
1065
#    def test_add_node_two_oversized_third_kept_minimum_fan(self):
1066
#    def test_add_node_one_oversized_second_splits_errors(self):
1067
1068
    def test_iteritems_empty_new(self):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1069
        node = InternalNode()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1070
        self.assertEqual([], sorted(node.iteritems(None)))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1071
1072
    def test_iteritems_two_children(self):
1073
        node = InternalNode()
1074
        leaf1 = LeafNode()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1075
        leaf1.map(None, ('foo bar',), 'quux')
1076
        leaf2 = LeafNode()
1077
        leaf2.map(None, ('strange',), 'beast')
3735.2.24 by Robert Collins
test_chk_map tests all passing.
1078
        node.add_node("f", leaf1)
1079
        node.add_node("s", leaf2)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1080
        self.assertEqual([(('foo bar',), 'quux'), (('strange',), 'beast')],
3735.2.24 by Robert Collins
test_chk_map tests all passing.
1081
            sorted(node.iteritems(None)))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1082
1083
    def test_iteritems_two_children_partial(self):
1084
        node = InternalNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
1085
        leaf1 = LeafNode()
1086
        leaf1.map(None, ('foo bar',), 'quux')
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1087
        leaf2 = LeafNode()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1088
        leaf2.map(None, ('strange',), 'beast')
3735.2.24 by Robert Collins
test_chk_map tests all passing.
1089
        node.add_node("f", leaf1)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1090
        # This sets up a path that should not be followed - it will error if
1091
        # the code tries to.
3735.2.24 by Robert Collins
test_chk_map tests all passing.
1092
        node._items['f'] = None
1093
        node.add_node("s", leaf2)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1094
        self.assertEqual([(('strange',), 'beast')],
3735.2.24 by Robert Collins
test_chk_map tests all passing.
1095
            sorted(node.iteritems(None, [('strange',), ('weird',)])))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1096
1097
    def test_iteritems_partial_empty(self):
1098
        node = InternalNode()
1099
        self.assertEqual([], sorted(node.iteritems([('missing',)])))
1100
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1101
    def test_map_to_new_child_new(self):
1102
        chkmap = self._get_map({('k1',):'foo', ('k2',):'bar'}, maximum_size=10)
1103
        chkmap._ensure_root()
1104
        node = chkmap._root_node
1105
        # Ensure test validity: nothing paged in below the root.
1106
        self.assertEqual(2,
1107
            len([value for value in node._items.values()
1108
                if type(value) == tuple]))
1109
        # now, mapping to k3 should add a k3 leaf
1110
        prefix, nodes = node.map(None, ('k3',), 'quux')
1111
        self.assertEqual("k", prefix)
1112
        self.assertEqual([("", node)], nodes)
1113
        # check new child details
1114
        child = node._items['k3']
1115
        self.assertIsInstance(child, LeafNode)
1116
        self.assertEqual(1, len(child))
1117
        self.assertEqual({('k3',): 'quux'}, self.to_dict(child, None))
1118
        self.assertEqual(None, child._key)
1119
        self.assertEqual(10, child.maximum_size)
1120
        self.assertEqual(1, child._key_width)
1121
        # Check overall structure:
1122
        self.assertEqual(3, len(chkmap))
1123
        self.assertEqual({('k1',): 'foo', ('k2',): 'bar', ('k3',): 'quux'},
1124
            self.to_dict(chkmap))
1125
        # serialising should only serialise the new data - k3 and the internal
1126
        # node.
1127
        keys = list(node.serialise(chkmap._store))
1128
        child_key = child.serialise(chkmap._store)[0]
1129
        self.assertEqual([child_key, keys[1]], keys)
1130
1131
    def test_map_to_child_child_splits_new(self):
1132
        chkmap = self._get_map({('k1',):'foo', ('k22',):'bar'}, maximum_size=10)
1133
        # Check for the canonical root value for this tree:
1134
        self.assertEqual(('sha1:d3f06fc03d8f50845894d8d04cc5a3f47e62948d',),
1135
            chkmap._root_node)
1136
        chkmap._ensure_root()
1137
        node = chkmap._root_node
1138
        # Ensure test validity: nothing paged in below the root.
1139
        self.assertEqual(2,
1140
            len([value for value in node._items.values()
1141
                if type(value) == tuple]))
1142
        # now, mapping to k23 causes k22 ('k2' in node) to split into k22 and
1143
        # k23, which for simplicity in the current implementation generates
1144
        # a new internal node between node, and k22/k23.
1145
        prefix, nodes = node.map(chkmap._store, ('k23',), 'quux')
1146
        self.assertEqual("k", prefix)
1147
        self.assertEqual([("", node)], nodes)
1148
        # check new child details
1149
        child = node._items['k2']
1150
        self.assertIsInstance(child, InternalNode)
1151
        self.assertEqual(2, len(child))
1152
        self.assertEqual({('k22',): 'bar', ('k23',): 'quux'},
1153
            self.to_dict(child, None))
1154
        self.assertEqual(None, child._key)
1155
        self.assertEqual(10, child.maximum_size)
1156
        self.assertEqual(1, child._key_width)
1157
        self.assertEqual(3, child._node_width)
1158
        # Check overall structure:
1159
        self.assertEqual(3, len(chkmap))
1160
        self.assertEqual({('k1',): 'foo', ('k22',): 'bar', ('k23',): 'quux'},
1161
            self.to_dict(chkmap))
1162
        # serialising should only serialise the new data - although k22 hasn't
1163
        # changed because its a special corner case (splitting on with only one
1164
        # key leaves one node unaltered), in general k22 is serialised, so we
1165
        # expect k22, k23, the new internal node, and node, to be serialised.
1166
        keys = list(node.serialise(chkmap._store))
1167
        child_key = child._key
1168
        k22_key = child._items['k22']._key
1169
        k23_key = child._items['k23']._key
1170
        self.assertEqual([k22_key, k23_key, child_key, keys[-1]], keys)
1171
        self.assertEqual(('sha1:d68cd97c95e847d3dc58c05537aa5fdcdf2cf5da',),
1172
            keys[-1])
1173
1174
    def test_unmap_k23_from_k1_k22_k23_gives_k1_k22_tree_new(self):
1175
        chkmap = self._get_map(
1176
            {('k1',):'foo', ('k22',):'bar', ('k23',): 'quux'}, maximum_size=10)
1177
        # Check we have the expected tree.
1178
        self.assertEqual(('sha1:d68cd97c95e847d3dc58c05537aa5fdcdf2cf5da',),
1179
            chkmap._root_node)
1180
        chkmap._ensure_root()
1181
        node = chkmap._root_node
1182
        # unmapping k23 should give us a root, with k1 and k22 as direct
1183
        # children.
1184
        result = node.unmap(chkmap._store, ('k23',))
1185
        # check the pointed-at object within node - k2 should now point at the
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
1186
        # k22 leaf (which has been paged in to see if we can collapse the tree)
1187
        child = node._items['k2']
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1188
        self.assertIsInstance(child, LeafNode)
1189
        self.assertEqual(1, len(child))
1190
        self.assertEqual({('k22',): 'bar'},
1191
            self.to_dict(child, None))
1192
        # Check overall structure is instact:
1193
        self.assertEqual(2, len(chkmap))
1194
        self.assertEqual({('k1',): 'foo', ('k22',): 'bar'},
1195
            self.to_dict(chkmap))
1196
        # serialising should only serialise the new data - the root node.
1197
        keys = list(node.serialise(chkmap._store))
1198
        self.assertEqual([keys[-1]], keys)
1199
        self.assertEqual(('sha1:d3f06fc03d8f50845894d8d04cc5a3f47e62948d',), keys[-1])
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1200
3735.2.23 by Robert Collins
Test unmapping with one child left but multiple keys.
1201
    def test_unmap_k1_from_k1_k22_k23_gives_k22_k23_tree_new(self):
1202
        chkmap = self._get_map(
1203
            {('k1',):'foo', ('k22',):'bar', ('k23',): 'quux'}, maximum_size=10)
1204
        # Check we have the expected tree.
1205
        self.assertEqual(('sha1:d68cd97c95e847d3dc58c05537aa5fdcdf2cf5da',),
1206
            chkmap._root_node)
1207
        chkmap._ensure_root()
1208
        node = chkmap._root_node
1209
        k2_ptr = node._items['k2']
1210
        # unmapping k21 should give us a root, with k22 and k23 as direct
1211
        # children, and should not have needed to page in the subtree.
1212
        result = node.unmap(chkmap._store, ('k1',))
1213
        self.assertEqual(k2_ptr, result)
1214
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1215
1216
# leaf:
1217
# map -> fits - done
1218
# map -> doesn't fit - shrink from left till fits
1219
#        key data to return: the common prefix, new nodes.
1220
1221
# unmap -> how to tell if siblings can be combined.
1222
#          combing leaf nodes means expanding the prefix to the left; so gather the size of
1223
#          all the leaf nodes addressed by expanding the prefix by 1; if any adjacent node
1224
#          is an internal node, we know that that is a dense subtree - can't combine.
1225
#          otherwise as soon as the sum of serialised values exceeds the split threshold
1226
#          we know we can't combine - stop.
1227
# unmap -> key return data - space in node, common prefix length? and key count
1228
# internal: 
1229
# variable length prefixes? -> later start with fixed width to get something going
1230
# map -> fits - update pointer to leaf
1231
#        return [prefix and node] - seems sound.
1232
# map -> doesn't fit - find unique prefix and shift right
1233
#        create internal nodes for all the partitions, return list of unique
1234
#        prefixes and nodes.
1235
# map -> new prefix - create a leaf
1236
# unmap -> if child key count 0, remove
1237
# unmap -> return space in node, common prefix length? (why?), key count
1238
# map:
1239
# map, if 1 node returned, use it, otherwise make an internal and populate.
1240
# map - unmap - if empty, use empty leafnode (avoids special cases in driver
1241
# code)
1242
# map inits as empty leafnode.
1243
# tools: 
1244
# visualiser
1245
1246
1247
# how to handle:
1248
# AA, AB, AC, AD, BA
1249
# packed internal node - ideal:
1250
# AA, AB, AC, AD, BA
1251
# single byte fanout - A,B,   AA,AB,AC,AD,     BA
1252
# build order's:
1253
# BA
1254
# AB - split, but we want to end up with AB, BA, in one node, with 
1255
# 1-4K get0
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1256
1257
1258
class TestIterInterestingNodes(TestCaseWithStore):
1259
1260
    def get_chk_bytes(self):
1261
        if getattr(self, '_chk_bytes', None) is None:
1262
            self._chk_bytes = super(TestIterInterestingNodes,
1263
                                    self).get_chk_bytes()
1264
        return self._chk_bytes
1265
1266
    def get_map_key(self, a_dict):
1267
        c_map = self._get_map(a_dict, maximum_size=10,
1268
                              chk_bytes=self.get_chk_bytes())
1269
        return c_map.key()
1270
1271
    def assertIterInteresting(self, expected, interesting_keys,
1272
                              uninteresting_keys):
1273
        """Check the result of iter_interesting_nodes.
1274
1275
        :param expected: A list of (record_keys, interesting_chk_pages,
1276
                                    interesting key value pairs)
1277
        """
1278
        store = self.get_chk_bytes()
1279
        iter_nodes = chk_map.iter_interesting_nodes(store, interesting_keys,
1280
                                                    uninteresting_keys)
1281
        for count, (exp, act) in enumerate(izip(expected, iter_nodes)):
3735.9.8 by John Arbash Meinel
Simplify the interface.
1282
            exp_record_keys, exp_items = exp
1283
            records, items = act
3735.9.15 by John Arbash Meinel
Found a bug in iter_interesting_nodes and its test suite.
1284
            exp_tuple = (sorted(exp_record_keys), sorted(exp_items))
1285
            act_tuple = (sorted(records.keys()), sorted(items))
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1286
            self.assertEqual(exp_tuple, act_tuple)
1287
        self.assertEqual(len(expected), count + 1)
1288
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1289
    def test_empty_to_one_keys(self):
1290
        target = self.get_map_key({('a',): 'content'})
1291
        self.assertIterInteresting(
1292
            [([target], [(('a',), 'content')])],
1293
            [target], [])
1294
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1295
    def test_none_to_one_key(self):
1296
        basis = self.get_map_key({})
1297
        target = self.get_map_key({('a',): 'content'})
1298
        self.assertIterInteresting(
3735.9.8 by John Arbash Meinel
Simplify the interface.
1299
            [([target], [(('a',), 'content')])],
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1300
            [target], [basis])
1301
1302
    def test_one_to_none_key(self):
1303
        basis = self.get_map_key({('a',): 'content'})
1304
        target = self.get_map_key({})
1305
        self.assertIterInteresting(
3735.9.8 by John Arbash Meinel
Simplify the interface.
1306
            [([target], [])],
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1307
            [target], [basis])
1308
1309
    def test_common_pages(self):
1310
        basis = self.get_map_key({('a',): 'content',
1311
                                  ('b',): 'content',
1312
                                  ('c',): 'content',
1313
                                 })
1314
        target = self.get_map_key({('a',): 'content',
1315
                                   ('b',): 'other content',
1316
                                   ('c',): 'content',
1317
                                  })
1318
        # Is there a way to get this more directly?
1319
        b_key = ('sha1:1d7a45ded01ab77c069350c0e290ae34db5b549b',)
1320
        # This should return the root node, and the node for the 'b' key
1321
        self.assertIterInteresting(
3735.9.8 by John Arbash Meinel
Simplify the interface.
1322
            [([target], []),
1323
             ([b_key], [(('b',), 'other content')])],
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1324
            [target], [basis])
1325
1326
    def test_common_sub_page(self):
1327
        basis = self.get_map_key({('aaa',): 'common',
1328
                                  ('c',): 'common',
1329
                                 })
1330
        target = self.get_map_key({('aaa',): 'common',
1331
                                   ('aab',): 'new',
1332
                                   ('c',): 'common',
1333
                                  })
3735.9.6 by John Arbash Meinel
Add a first pass to the interesting search.
1334
        self.assertEqualDiff(
1335
            "'' InternalNode sha1:f88b38806015efe27013260d7402219b7b4d4332\n"
1336
            "  'a' InternalNode sha1:2ce01860338a614b93883a5bbeb89920137ac7ef\n"
1337
            "    'aaa' LeafNode sha1:0b38f800c49ff9ffae346ca6f7e80a4626a5eaca\n"
1338
            "      ('aaa',) 'common'\n"
1339
            "    'aab' LeafNode sha1:10567a3bfcc764fb8d8d9edaa28c0934ada366c5\n"
1340
            "      ('aab',) 'new'\n"
1341
            "  'c' LeafNode sha1:263208de2fce0a8f9db614c1ca39e8f6de8b3802\n"
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
1342
            "      ('c',) 'common'\n",
3735.9.6 by John Arbash Meinel
Add a first pass to the interesting search.
1343
            CHKMap(self.get_chk_bytes(), target)._dump_tree())
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1344
        # The key for the internal aa node
1345
        aa_key = ('sha1:2ce01860338a614b93883a5bbeb89920137ac7ef',)
1346
        # The key for the leaf aab node
1347
        aab_key = ('sha1:10567a3bfcc764fb8d8d9edaa28c0934ada366c5',)
1348
        self.assertIterInteresting(
3735.9.8 by John Arbash Meinel
Simplify the interface.
1349
            [([target], []),
1350
             ([aa_key], []),
1351
             ([aab_key], [(('aab',), 'new')])],
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1352
            [target], [basis])
1353
1354
    def test_multiple_maps(self):
1355
        basis1 = self.get_map_key({('aaa',): 'common',
1356
                                   ('aab',): 'basis1',
1357
                                  })
1358
        basis2 = self.get_map_key({('bbb',): 'common',
1359
                                   ('bbc',): 'basis2',
1360
                                  })
1361
        target1 = self.get_map_key({('aaa',): 'common',
1362
                                    ('aac',): 'target1',
1363
                                    ('bbb',): 'common',
1364
                                   })
1365
        target2 = self.get_map_key({('aaa',): 'common',
1366
                                    ('bba',): 'target2',
1367
                                    ('bbb',): 'common',
1368
                                   })
1369
        # The key for the target1 internal aa node
1370
        aa_key = ('sha1:4c6b1e3e6ecb68fe039d2b00c9091bc037ebf203',)
1371
        # The key for the leaf aac node
1372
        aac_key = ('sha1:8089f6b4f3bd2a058c41be199ef5af0c5b9a0c4f',)
1373
        # The key for the target2 internal bb node
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
1374
        bb_key = ('sha1:bcc229e6bd1d606ef4630073dc15756e60508365',)
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1375
        # The key for the leaf bba node
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
1376
        bba_key = ('sha1:5ce6a69a21060222bb0a5b48fdbfcca586cc9183',)
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1377
        self.assertIterInteresting(
3735.9.8 by John Arbash Meinel
Simplify the interface.
1378
            [([target1, target2], []),
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1379
             ([aa_key], []),
1380
             ([bb_key], []),
1381
             ([aac_key], [(('aac',), 'target1')]),
1382
             ([bba_key], [(('bba',), 'target2')]),
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1383
            ], [target1, target2], [basis1, basis2])