/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.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
73
    def test_from_dict_empty(self):
74
        chk_bytes = self.get_chk_bytes()
75
        root_key = CHKMap.from_dict(chk_bytes, {})
3735.2.24 by Robert Collins
test_chk_map tests all passing.
76
        # Check the data was saved and inserted correctly.
77
        expected_root_key = self.assertHasEmptyMap(chk_bytes)
78
        self.assertEqual(expected_root_key, root_key)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
79
80
    def test_from_dict_ab(self):
81
        chk_bytes = self.get_chk_bytes()
82
        root_key = CHKMap.from_dict(chk_bytes, {"a":"b"})
3735.2.24 by Robert Collins
test_chk_map tests all passing.
83
        # Check the data was saved and inserted correctly.
84
        expected_root_key = self.assertHasABMap(chk_bytes)
85
        self.assertEqual(expected_root_key, root_key)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
86
87
    def test_apply_empty_ab(self):
88
        # applying a delta (None, "a", "b") to an empty chkmap generates the
89
        # same map as from_dict_ab.
90
        chk_bytes = self.get_chk_bytes()
91
        root_key = CHKMap.from_dict(chk_bytes, {})
92
        chkmap = CHKMap(chk_bytes, root_key)
93
        new_root = chkmap.apply_delta([(None, "a", "b")])
3735.2.24 by Robert Collins
test_chk_map tests all passing.
94
        # Check the data was saved and inserted correctly.
95
        expected_root_key = self.assertHasABMap(chk_bytes)
96
        self.assertEqual(expected_root_key, new_root)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
97
        # The update should have left us with an in memory root node, with an
98
        # updated key.
99
        self.assertEqual(new_root, chkmap._root_node._key)
100
101
    def test_apply_ab_empty(self):
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
102
        # applying a delta ("a", None, None) to a map with 'a' in it generates
103
        # an empty map.
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
104
        chk_bytes = self.get_chk_bytes()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
105
        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.
106
        chkmap = CHKMap(chk_bytes, root_key)
3735.2.24 by Robert Collins
test_chk_map tests all passing.
107
        new_root = chkmap.apply_delta([(("a",), None, None)])
108
        # Check the data was saved and inserted correctly.
109
        expected_root_key = self.assertHasEmptyMap(chk_bytes)
110
        self.assertEqual(expected_root_key, new_root)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
111
        # The update should have left us with an in memory root node, with an
112
        # updated key.
113
        self.assertEqual(new_root, chkmap._root_node._key)
114
3735.9.3 by John Arbash Meinel
Add a direct test case that shows how the map() function is not
115
    def test_apply_delta_is_deterministic(self):
116
        chk_bytes = self.get_chk_bytes()
117
        chkmap1 = CHKMap(chk_bytes, None)
118
        chkmap1._root_node.set_maximum_size(10)
119
        chkmap1.apply_delta([(None, ('aaa',), 'common'),
120
                             (None, ('bba',), 'target2'),
121
                             (None, ('bbb',), 'common')])
122
        root_key1 = chkmap1._save()
123
        chkmap2 = CHKMap(chk_bytes, None)
124
        chkmap2._root_node.set_maximum_size(10)
125
        chkmap2.apply_delta([(None, ('bbb',), 'common'),
126
                             (None, ('bba',), 'target2'),
127
                             (None, ('aaa',), 'common')])
128
        root_key2 = chkmap2._save()
129
        self.assertEqualDiff(chkmap1._dump_tree(), chkmap2._dump_tree())
130
        self.assertEqual(root_key1, root_key2)
131
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
132
    def test_stable_splitting(self):
133
        store = self.get_chk_bytes()
134
        chkmap = CHKMap(store, None)
135
        # Should fit 2 keys per LeafNode
136
        chkmap._root_node.set_maximum_size(30)
137
        chkmap.map(('aaa',), 'v')
138
        self.assertEqualDiff("'' LeafNode None\n"
139
                             "      ('aaa',) 'v'",
140
                             chkmap._dump_tree())
141
        chkmap.map(('aab',), 'v')
142
        self.assertEqualDiff("'' LeafNode None\n"
143
                             "      ('aaa',) 'v'\n"
144
                             "      ('aab',) 'v'",
145
                             chkmap._dump_tree())
146
        # Creates a new internal node, and splits the others into leaves
147
        chkmap.map(('aac',), 'v')
148
        self.assertEqualDiff("'' InternalNode None\n"
149
                             "  'aaa' LeafNode None\n"
150
                             "      ('aaa',) 'v'\n"
151
                             "  'aab' LeafNode None\n"
152
                             "      ('aab',) 'v'\n"
153
                             "  'aac' LeafNode None\n"
154
                             "      ('aac',) 'v'",
155
                             chkmap._dump_tree())
156
        # Splits again, because it can't fit in the current structure
157
        chkmap.map(('bbb',), 'v')
158
        self.assertEqualDiff("'' InternalNode None\n"
159
                             "  'a' InternalNode None\n"
160
                             "    'aaa' LeafNode None\n"
161
                             "      ('aaa',) 'v'\n"
162
                             "    'aab' LeafNode None\n"
163
                             "      ('aab',) 'v'\n"
164
                             "    'aac' LeafNode None\n"
165
                             "      ('aac',) 'v'\n"
166
                             "  'b' LeafNode None\n"
167
                             "      ('bbb',) 'v'",
168
                             chkmap._dump_tree())
169
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
170
    def test_deep_splitting(self):
171
        store = self.get_chk_bytes()
172
        chkmap = CHKMap(store, None)
173
        # Should fit 2 keys per LeafNode
174
        chkmap._root_node.set_maximum_size(40)
175
        chkmap.map(('aaaaaaaa',), 'v')
176
        chkmap.map(('aaaaabaa',), 'v')
177
        self.assertEqualDiff("'' LeafNode None\n"
178
                             "      ('aaaaaaaa',) 'v'\n"
179
                             "      ('aaaaabaa',) 'v'",
180
                             chkmap._dump_tree())
181
        chkmap.map(('aaabaaaa',), 'v')
182
        chkmap.map(('aaababaa',), 'v')
183
        self.assertEqualDiff("'' InternalNode None\n"
184
                             "  'aaaa' LeafNode None\n"
185
                             "      ('aaaaaaaa',) 'v'\n"
186
                             "      ('aaaaabaa',) 'v'\n"
187
                             "  'aaab' LeafNode None\n"
188
                             "      ('aaabaaaa',) 'v'\n"
189
                             "      ('aaababaa',) 'v'",
190
                             chkmap._dump_tree())
191
        chkmap.map(('aaabacaa',), 'v')
192
        chkmap.map(('aaabadaa',), 'v')
193
        self.assertEqualDiff("'' InternalNode None\n"
194
                             "  'aaaa' LeafNode None\n"
195
                             "      ('aaaaaaaa',) 'v'\n"
196
                             "      ('aaaaabaa',) 'v'\n"
197
                             "  'aaab' InternalNode None\n"
198
                             "    'aaabaa' LeafNode None\n"
199
                             "      ('aaabaaaa',) 'v'\n"
200
                             "    'aaabab' LeafNode None\n"
201
                             "      ('aaababaa',) 'v'\n"
202
                             "    'aaabac' LeafNode None\n"
203
                             "      ('aaabacaa',) 'v'\n"
204
                             "    'aaabad' LeafNode None\n"
205
                             "      ('aaabadaa',) 'v'",
206
                             chkmap._dump_tree())
207
        chkmap.map(('aaababba',), 'v')
208
        chkmap.map(('aaababca',), 'v')
209
        self.assertEqualDiff("'' InternalNode None\n"
210
                             "  'aaaa' LeafNode None\n"
211
                             "      ('aaaaaaaa',) 'v'\n"
212
                             "      ('aaaaabaa',) 'v'\n"
213
                             "  'aaab' InternalNode None\n"
214
                             "    'aaabaa' LeafNode None\n"
215
                             "      ('aaabaaaa',) 'v'\n"
216
                             "    'aaabab' InternalNode None\n"
217
                             "      'aaababa' LeafNode None\n"
218
                             "      ('aaababaa',) 'v'\n"
219
                             "      'aaababb' LeafNode None\n"
220
                             "      ('aaababba',) 'v'\n"
221
                             "      'aaababc' LeafNode None\n"
222
                             "      ('aaababca',) 'v'\n"
223
                             "    'aaabac' LeafNode None\n"
224
                             "      ('aaabacaa',) 'v'\n"
225
                             "    'aaabad' LeafNode None\n"
226
                             "      ('aaabadaa',) 'v'",
227
                             chkmap._dump_tree())
228
        # Now we add a node that should fit around an existing InternalNode,
229
        # but has a slightly different key prefix, which causes a new
230
        # InternalNode split
231
        chkmap.map(('aaabDaaa',), 'v')
232
        self.assertEqualDiff("'' InternalNode None\n"
233
                             "  'aaaa' LeafNode None\n"
234
                             "      ('aaaaaaaa',) 'v'\n"
235
                             "      ('aaaaabaa',) 'v'\n"
236
                             "  'aaab' InternalNode None\n"
237
                             "    'aaabD' LeafNode None\n"
238
                             "      ('aaabDaaa',) 'v'\n"
239
                             "    'aaaba' InternalNode None\n"
240
                             "      'aaabaa' LeafNode None\n"
241
                             "      ('aaabaaaa',) 'v'\n"
242
                             "      'aaabab' InternalNode None\n"
243
                             "        'aaababa' LeafNode None\n"
244
                             "      ('aaababaa',) 'v'\n"
245
                             "        'aaababb' LeafNode None\n"
246
                             "      ('aaababba',) 'v'\n"
247
                             "        'aaababc' LeafNode None\n"
248
                             "      ('aaababca',) 'v'\n"
249
                             "      'aaabac' LeafNode None\n"
250
                             "      ('aaabacaa',) 'v'\n"
251
                             "      'aaabad' LeafNode None\n"
252
                             "      ('aaabadaa',) 'v'",
253
                             chkmap._dump_tree())
254
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
255
    def test_iter_changes_empty_ab(self):
256
        # Asking for changes between an empty dict to a dict with keys returns
257
        # all the keys.
3735.2.31 by Robert Collins
CHKMap.iter_changes
258
        basis = self._get_map({}, maximum_size=10)
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
259
        target = self._get_map(
260
            {('a',): 'content here', ('b',): 'more content'},
3735.2.31 by Robert Collins
CHKMap.iter_changes
261
            chk_bytes=basis._store, maximum_size=10)
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
262
        self.assertEqual([(('a',), None, 'content here'),
3735.2.31 by Robert Collins
CHKMap.iter_changes
263
            (('b',), None, 'more content')],
264
            sorted(list(target.iter_changes(basis))))
265
266
    def test_iter_changes_ab_empty(self):
267
        # Asking for changes between a dict with keys to an empty dict returns
268
        # all the keys.
269
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
270
            maximum_size=10)
271
        target = self._get_map({}, chk_bytes=basis._store, maximum_size=10)
272
        self.assertEqual([(('a',), 'content here', None),
273
            (('b',), 'more content', None)],
274
            sorted(list(target.iter_changes(basis))))
275
276
    def test_iter_changes_empty_empty_is_empty(self):
277
        basis = self._get_map({}, maximum_size=10)
278
        target = self._get_map({}, chk_bytes=basis._store, maximum_size=10)
279
        self.assertEqual([], sorted(list(target.iter_changes(basis))))
280
281
    def test_iter_changes_ab_ab_is_empty(self):
282
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
283
            maximum_size=10)
284
        target = self._get_map(
285
            {('a',): 'content here', ('b',): 'more content'},
286
            chk_bytes=basis._store, maximum_size=10)
287
        self.assertEqual([], sorted(list(target.iter_changes(basis))))
288
289
    def test_iter_changes_ab_ab_nodes_not_loaded(self):
290
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
291
            maximum_size=10)
292
        target = self._get_map(
293
            {('a',): 'content here', ('b',): 'more content'},
294
            chk_bytes=basis._store, maximum_size=10)
295
        list(target.iter_changes(basis))
296
        self.assertIsInstance(target._root_node, tuple)
297
        self.assertIsInstance(basis._root_node, tuple)
298
299
    def test_iter_changes_ab_ab_changed_values_shown(self):
300
        basis = self._get_map({('a',): 'content here', ('b',): 'more content'},
301
            maximum_size=10)
302
        target = self._get_map(
303
            {('a',): 'content here', ('b',): 'different content'},
304
            chk_bytes=basis._store, maximum_size=10)
305
        result = sorted(list(target.iter_changes(basis)))
306
        self.assertEqual([(('b',), 'more content', 'different content')],
307
            result)
308
309
    def test_iter_changes_mixed_node_length(self):
310
        # When one side has different node lengths than the other, common
311
        # but different keys still need to be show, and new-and-old included
312
        # appropriately.
313
        # aaa - common unaltered
314
        # aab - common altered
315
        # b - basis only
316
        # at - target only
317
        # we expect: 
318
        # aaa to be not loaded (later test)
319
        # aab, b, at to be returned.
320
        # basis splits at byte 0,1,2, aaa is commonb is basis only
321
        basis_dict = {('aaa',): 'foo bar',
322
            ('aab',): 'common altered a', ('b',): 'foo bar b'}
323
        # target splits at byte 1,2, at is target only
324
        target_dict = {('aaa',): 'foo bar',
325
            ('aab',): 'common altered b', ('at',): 'foo bar t'}
326
        changes = [
327
            (('aab',), 'common altered a', 'common altered b'),
328
            (('at',), None, 'foo bar t'),
329
            (('b',), 'foo bar b', None),
330
            ]
331
        basis = self._get_map(basis_dict, maximum_size=10)
332
        target = self._get_map(target_dict, maximum_size=10,
333
            chk_bytes=basis._store)
334
        self.assertEqual(changes, sorted(list(target.iter_changes(basis))))
335
336
    def test_iter_changes_common_pages_not_loaded(self):
337
        # aaa - common unaltered
338
        # aab - common altered
339
        # b - basis only
340
        # at - target only
341
        # we expect: 
342
        # aaa to be not loaded
343
        # aaa not to be in result.
344
        basis_dict = {('aaa',): 'foo bar',
345
            ('aab',): 'common altered a', ('b',): 'foo bar b'}
346
        # target splits at byte 1, at is target only
347
        target_dict = {('aaa',): 'foo bar',
348
            ('aab',): 'common altered b', ('at',): 'foo bar t'}
349
        basis = self._get_map(basis_dict, maximum_size=10)
350
        target = self._get_map(target_dict, maximum_size=10,
351
            chk_bytes=basis._store)
3735.2.32 by Robert Collins
Activate test for common node skipping. - 50 times performance improvement.
352
        basis_get = basis._store.get_record_stream
353
        def get_record_stream(keys, order, fulltext):
354
            if ('sha1:1adf7c0d1b9140ab5f33bb64c6275fa78b1580b7',) in keys:
355
                self.fail("'aaa' pointer was followed %r" % keys)
356
            return basis_get(keys, order, fulltext)
357
        basis._store.get_record_stream = get_record_stream
3735.2.31 by Robert Collins
CHKMap.iter_changes
358
        result = sorted(list(target.iter_changes(basis)))
359
        for change in result:
360
            if change[0] == ('aaa',):
361
                self.fail("Found unexpected change: %s" % change)
362
363
    def test_iter_changes_unchanged_keys_in_multi_key_leafs_ignored(self):
364
        # Within a leaf there are no hash's to exclude keys, make sure multi
365
        # value leaf nodes are handled well.
366
        basis_dict = {('aaa',): 'foo bar',
367
            ('aab',): 'common altered a', ('b',): 'foo bar b'}
368
        target_dict = {('aaa',): 'foo bar',
369
            ('aab',): 'common altered b', ('at',): 'foo bar t'}
370
        changes = [
371
            (('aab',), 'common altered a', 'common altered b'),
372
            (('at',), None, 'foo bar t'),
373
            (('b',), 'foo bar b', None),
374
            ]
375
        basis = self._get_map(basis_dict)
376
        target = self._get_map(target_dict, chk_bytes=basis._store)
377
        self.assertEqual(changes, sorted(list(target.iter_changes(basis))))
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
378
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
379
    def test_iteritems_empty(self):
380
        chk_bytes = self.get_chk_bytes()
381
        root_key = CHKMap.from_dict(chk_bytes, {})
382
        chkmap = CHKMap(chk_bytes, root_key)
383
        self.assertEqual([], list(chkmap.iteritems()))
384
385
    def test_iteritems_two_items(self):
386
        chk_bytes = self.get_chk_bytes()
387
        root_key = CHKMap.from_dict(chk_bytes,
388
            {"a":"content here", "b":"more content"})
389
        chkmap = CHKMap(chk_bytes, root_key)
3735.2.24 by Robert Collins
test_chk_map tests all passing.
390
        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.
391
            sorted(list(chkmap.iteritems())))
392
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
393
    def test_iteritems_selected_one_of_two_items(self):
3735.2.24 by Robert Collins
test_chk_map tests all passing.
394
        chkmap = self._get_map( {("a",):"content here", ("b",):"more content"})
395
        self.assertEqual({("a",): "content here"},
396
            self.to_dict(chkmap, [("a",)]))
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
397
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
398
    def test_iteritems_keys_prefixed_by_2_width_nodes(self):
399
        chkmap = self._get_map(
400
            {("a","a"):"content here", ("a", "b",):"more content",
401
             ("b", ""): 'boring content'},
402
            maximum_size=10, key_width=2)
403
        self.assertEqual(
404
            {("a", "a"): "content here", ("a", "b"): 'more content'},
405
            self.to_dict(chkmap, [("a",)]))
406
407
    def test_iteritems_keys_prefixed_by_2_width_one_leaf(self):
408
        chkmap = self._get_map(
409
            {("a","a"):"content here", ("a", "b",):"more content",
410
             ("b", ""): 'boring content'}, key_width=2)
411
        self.assertEqual(
412
            {("a", "a"): "content here", ("a", "b"): 'more content'},
413
            self.to_dict(chkmap, [("a",)]))
414
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
415
    def test___len__empty(self):
416
        chkmap = self._get_map({})
417
        self.assertEqual(0, len(chkmap))
418
419
    def test___len__2(self):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
420
        chkmap = self._get_map({"foo":"bar", "gam":"quux"})
421
        self.assertEqual(2, len(chkmap))
422
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
423
    def test_max_size_100_bytes_new(self):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
424
        # 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.
425
        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.
426
        # We expect three nodes:
427
        # A root, with two children, and with two key prefixes - k1 to one, and
428
        # k2 to the other as our node splitting is only just being developed.
429
        # The maximum size should be embedded
430
        chkmap._ensure_root()
431
        self.assertEqual(100, chkmap._root_node.maximum_size)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
432
        self.assertEqual(1, chkmap._root_node._key_width)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
433
        # 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.
434
        self.assertEqual(2, len(chkmap._root_node._items))
435
        self.assertEqual("k", chkmap._root_node.unique_serialised_prefix())
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
436
        # The actual nodes pointed at will change as serialisers change; so
437
        # here we test that the key prefix is correct; then load the nodes and
438
        # check they have the right pointed at key; whether they have the
439
        # 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.
440
        # don't check that in detail - rather we just check the aggregate
441
        # value.
442
        nodes = sorted(chkmap._root_node._items.items())
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
443
        ptr1 = nodes[0]
444
        ptr2 = nodes[1]
445
        self.assertEqual('k1', ptr1[0])
446
        self.assertEqual('k2', ptr2[0])
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
447
        node1 = _deserialise(chkmap._read_bytes(ptr1[1]), ptr1[1])
448
        self.assertIsInstance(node1, LeafNode)
449
        self.assertEqual(1, len(node1))
450
        self.assertEqual({('k1'*50,): 'v1'}, self.to_dict(node1, chkmap._store))
451
        node2 = _deserialise(chkmap._read_bytes(ptr2[1]), ptr2[1])
452
        self.assertIsInstance(node2, LeafNode)
453
        self.assertEqual(1, len(node2))
454
        self.assertEqual({('k2'*50,): 'v2'}, self.to_dict(node2, chkmap._store))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
455
        # Having checked we have a good structure, check that the content is
456
        # still accessible.
457
        self.assertEqual(2, len(chkmap))
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
458
        self.assertEqual({("k1"*50,): "v1", ("k2"*50,): "v2"},
459
            self.to_dict(chkmap))
460
461
    def test_init_root_is_LeafNode_new(self):
462
        chk_bytes = self.get_chk_bytes()
463
        chkmap = CHKMap(chk_bytes, None)
464
        self.assertIsInstance(chkmap._root_node, LeafNode)
465
        self.assertEqual({}, self.to_dict(chkmap))
466
        self.assertEqual(0, len(chkmap))
467
468
    def test_init_and_save_new(self):
469
        chk_bytes = self.get_chk_bytes()
470
        chkmap = CHKMap(chk_bytes, None)
471
        key = chkmap._save()
472
        leaf_node = LeafNode()
473
        self.assertEqual([key], leaf_node.serialise(chk_bytes))
474
475
    def test_map_first_item_new(self):
476
        chk_bytes = self.get_chk_bytes()
477
        chkmap = CHKMap(chk_bytes, None)
478
        chkmap.map(("foo,",), "bar")
479
        self.assertEqual({('foo,',): 'bar'}, self.to_dict(chkmap))
480
        self.assertEqual(1, len(chkmap))
481
        key = chkmap._save()
482
        leaf_node = LeafNode()
483
        leaf_node.map(chk_bytes, ("foo,",), "bar")
484
        self.assertEqual([key], leaf_node.serialise(chk_bytes))
485
486
    def test_unmap_last_item_root_is_leaf_new(self):
487
        chkmap = self._get_map({("k1"*50,): "v1", ("k2"*50,): "v2"})
488
        chkmap.unmap(("k1"*50,))
489
        chkmap.unmap(("k2"*50,))
490
        self.assertEqual(0, len(chkmap))
491
        self.assertEqual({}, self.to_dict(chkmap))
492
        key = chkmap._save()
493
        leaf_node = LeafNode()
494
        self.assertEqual([key], leaf_node.serialise(chkmap._store))
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
495
3735.9.2 by John Arbash Meinel
Add a _dump_tree helper that assists in debugging what is going on.
496
    def test__dump_tree(self):
497
        chkmap = self._get_map({("aaa",): "value1", ("aab",): "value2",
498
                                ("bbb",): "value3",},
499
                               maximum_size=10)
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
500
        self.assertEqualDiff('\n'.join([
501
            "'' InternalNode sha1:cd9b68f18c9754a79065b06379fba543f9031742",
502
            "  'a' InternalNode sha1:ed0ceb5aeb87c56df007a17997134328ff4d0b8d",
503
            "    'aaa' LeafNode sha1:16fa5a38b80d29b529afc45f7a4f894650fc067f",
504
            "      ('aaa',) 'value1'",
505
            "    'aab' LeafNode sha1:8fca5400dc99ef1b464e60ca25da53b57406ed38",
506
            "      ('aab',) 'value2'",
507
            "  'b' LeafNode sha1:67f15d1dfa451d388ed08ff17b4f9578ba010d01",
508
            "      ('bbb',) 'value3'",
509
            ]), chkmap._dump_tree())
510
511
    def test__dump_tree_in_progress(self):
512
        chkmap = self._get_map({("aaa",): "value1", ("aab",): "value2"},
513
                               maximum_size=10)
514
        chkmap.map(('bbb',), 'value3')
515
        # XXX: Note that this representation is different than the one for
516
        #      test__dump_tree, even though they have the same values
517
        self.assertEqualDiff('\n'.join([
518
            "'' InternalNode None",
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
519
            "  'a' InternalNode sha1:ed0ceb5aeb87c56df007a17997134328ff4d0b8d",
520
            "    'aaa' LeafNode sha1:16fa5a38b80d29b529afc45f7a4f894650fc067f",
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
521
            "      ('aaa',) 'value1'",
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
522
            "    'aab' LeafNode sha1:8fca5400dc99ef1b464e60ca25da53b57406ed38",
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
523
            "      ('aab',) 'value2'",
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
524
            "  'b' LeafNode None",
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
525
            "      ('bbb',) 'value3'",
526
            ]), chkmap._dump_tree())
3735.9.2 by John Arbash Meinel
Add a _dump_tree helper that assists in debugging what is going on.
527
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
528
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
529
class TestLeafNode(TestCaseWithStore):
530
531
    def test_current_size_empty(self):
532
        node = LeafNode()
533
        self.assertEqual(15, node._current_size())
534
535
    def test_current_size_size_changed(self):
536
        node = LeafNode()
537
        node.set_maximum_size(10)
538
        self.assertEqual(16, node._current_size())
539
540
    def test_current_size_width_changed(self):
541
        node = LeafNode()
542
        node._key_width = 10
543
        self.assertEqual(16, node._current_size())
544
545
    def test_current_size_items(self):
546
        node = LeafNode()
547
        base_size = node._current_size()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
548
        node.map(None, ("foo bar",), "baz")
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
549
        self.assertEqual(base_size + 12, node._current_size())
550
551
    def test_deserialise_empty(self):
552
        node = LeafNode.deserialise("chkleaf:\n10\n1\n0\n", ("sha1:1234",))
553
        self.assertEqual(0, len(node))
554
        self.assertEqual(10, node.maximum_size)
555
        self.assertEqual(("sha1:1234",), node.key())
556
557
    def test_deserialise_items(self):
558
        node = LeafNode.deserialise(
559
            "chkleaf:\n0\n1\n2\nfoo bar\x00baz\nquux\x00blarh\n", ("sha1:1234",))
560
        self.assertEqual(2, len(node))
561
        self.assertEqual([(("foo bar",), "baz"), (("quux",), "blarh")],
3735.2.24 by Robert Collins
test_chk_map tests all passing.
562
            sorted(node.iteritems(None)))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
563
3735.2.25 by Robert Collins
CHKInventory core tests passing.
564
    def test_deserialise_item_with_null_width_1(self):
565
        node = LeafNode.deserialise(
566
            "chkleaf:\n0\n1\n2\nfoo\x00bar\x00baz\nquux\x00blarh\n",
567
            ("sha1:1234",))
568
        self.assertEqual(2, len(node))
569
        self.assertEqual([(("foo",), "bar\x00baz"), (("quux",), "blarh")],
570
            sorted(node.iteritems(None)))
571
572
    def test_deserialise_item_with_null_width_2(self):
573
        node = LeafNode.deserialise(
574
            "chkleaf:\n0\n2\n2\nfoo\x001\x00bar\x00baz\nquux\x00\x00blarh\n",
575
            ("sha1:1234",))
576
        self.assertEqual(2, len(node))
577
        self.assertEqual([(("foo", "1"), "bar\x00baz"), (("quux", ""), "blarh")],
578
            sorted(node.iteritems(None)))
579
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
580
    def test_iteritems_selected_one_of_two_items(self):
581
        node = LeafNode.deserialise(
582
            "chkleaf:\n0\n1\n2\nfoo bar\x00baz\nquux\x00blarh\n", ("sha1:1234",))
583
        self.assertEqual(2, len(node))
584
        self.assertEqual([(("quux",), "blarh")],
3735.2.24 by Robert Collins
test_chk_map tests all passing.
585
            sorted(node.iteritems(None, [("quux",), ("qaz",)])))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
586
587
    def test_key_new(self):
588
        node = LeafNode()
589
        self.assertEqual(None, node.key())
590
591
    def test_key_after_map(self):
592
        node = LeafNode.deserialise("chkleaf:\n10\n1\n0\n", ("sha1:1234",))
3735.2.24 by Robert Collins
test_chk_map tests all passing.
593
        node.map(None, ("foo bar",), "baz quux")
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
594
        self.assertEqual(None, node.key())
595
596
    def test_key_after_unmap(self):
597
        node = LeafNode.deserialise(
598
            "chkleaf:\n0\n1\n2\nfoo bar\x00baz\nquux\x00blarh\n", ("sha1:1234",))
3735.2.24 by Robert Collins
test_chk_map tests all passing.
599
        node.unmap(None, ("foo bar",))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
600
        self.assertEqual(None, node.key())
601
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
602
    def test_map_exceeding_max_size_only_entry_new(self):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
603
        node = LeafNode()
604
        node.set_maximum_size(10)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
605
        result = node.map(None, ("foo bar",), "baz quux")
606
        self.assertEqual(("foo bar", [("", node)]), result)
607
        self.assertTrue(10 < node._current_size())
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
608
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
609
    def test_map_exceeding_max_size_second_entry_early_difference_new(self):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
610
        node = LeafNode()
611
        node.set_maximum_size(10)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
612
        node.map(None, ("foo bar",), "baz quux")
613
        prefix, result = list(node.map(None, ("blue",), "red"))
614
        self.assertEqual("", prefix)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
615
        self.assertEqual(2, len(result))
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
616
        split_chars = set([result[0][0], result[1][0]])
617
        self.assertEqual(set(["f", "b"]), split_chars)
618
        nodes = dict(result)
619
        node = nodes["f"]
620
        self.assertEqual({("foo bar",): "baz quux"}, self.to_dict(node, None))
621
        self.assertEqual(10, node.maximum_size)
622
        self.assertEqual(1, node._key_width)
623
        node = nodes["b"]
624
        self.assertEqual({("blue",): "red"}, self.to_dict(node, None))
625
        self.assertEqual(10, node.maximum_size)
626
        self.assertEqual(1, node._key_width)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
627
628
    def test_map_first(self):
629
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
630
        result = node.map(None, ("foo bar",), "baz quux")
631
        self.assertEqual(("foo bar", [("", node)]), result)
632
        self.assertEqual({("foo bar",):"baz quux"}, self.to_dict(node, None))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
633
        self.assertEqual(1, len(node))
634
635
    def test_map_second(self):
636
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
637
        node.map(None, ("foo bar",), "baz quux")
638
        result = node.map(None, ("bingo",), "bango")
639
        self.assertEqual(("", [("", node)]), result)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
640
        self.assertEqual({("foo bar",):"baz quux", ("bingo",):"bango"},
3735.2.24 by Robert Collins
test_chk_map tests all passing.
641
            self.to_dict(node, None))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
642
        self.assertEqual(2, len(node))
643
644
    def test_map_replacement(self):
645
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
646
        node.map(None, ("foo bar",), "baz quux")
647
        result = node.map(None, ("foo bar",), "bango")
648
        self.assertEqual(("foo bar", [("", node)]), result)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
649
        self.assertEqual({("foo bar",): "bango"},
3735.2.24 by Robert Collins
test_chk_map tests all passing.
650
            self.to_dict(node, None))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
651
        self.assertEqual(1, len(node))
652
653
    def test_serialise_empty(self):
654
        store = self.get_chk_bytes()
655
        node = LeafNode()
656
        node.set_maximum_size(10)
657
        expected_key = ("sha1:62cc3565b48b0e830216e652cf99c6bd6b05b4b9",)
658
        self.assertEqual([expected_key],
659
            list(node.serialise(store)))
660
        self.assertEqual("chkleaf:\n10\n1\n0\n", self.read_bytes(store, expected_key))
661
        self.assertEqual(expected_key, node.key())
662
663
    def test_serialise_items(self):
664
        store = self.get_chk_bytes()
665
        node = LeafNode()
666
        node.set_maximum_size(10)
3735.2.24 by Robert Collins
test_chk_map tests all passing.
667
        node.map(None, ("foo bar",), "baz quux")
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
668
        expected_key = ("sha1:d44cb6f0299b7e047da7f9e98f810e98f1dce1a7",)
669
        self.assertEqual([expected_key],
670
            list(node.serialise(store)))
671
        self.assertEqual("chkleaf:\n10\n1\n1\nfoo bar\x00baz quux\n",
672
            self.read_bytes(store, expected_key))
673
        self.assertEqual(expected_key, node.key())
674
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
675
    def test_unique_serialised_prefix_empty_new(self):
676
        node = LeafNode()
677
        self.assertEqual("", node.unique_serialised_prefix())
678
679
    def test_unique_serialised_prefix_one_item_new(self):
680
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
681
        node.map(None, ("foo bar", "baz"), "baz quux")
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
682
        self.assertEqual("foo bar\x00baz", node.unique_serialised_prefix())
683
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
684
    def test_unmap_missing(self):
685
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
686
        self.assertRaises(KeyError, node.unmap, None, ("foo bar",))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
687
688
    def test_unmap_present(self):
689
        node = LeafNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
690
        node.map(None, ("foo bar",), "baz quux")
691
        result = node.unmap(None, ("foo bar",))
692
        self.assertEqual(node, result)
693
        self.assertEqual({}, self.to_dict(node, None))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
694
        self.assertEqual(0, len(node))
695
696
697
class TestInternalNode(TestCaseWithStore):
698
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
699
    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.
700
        node = InternalNode('fo')
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
701
        child = LeafNode()
702
        child.set_maximum_size(100)
703
        child.map(None, ("foo",), "bar")
704
        node.add_node("foo", child)
705
        # Note that node isn't strictly valid now as a tree (only one child),
706
        # but thats ok for this test.
707
        # The first child defines the node's width:
708
        self.assertEqual(3, node._node_width)
709
        # We should be able to iterate over the contents without doing IO.
710
        self.assertEqual({('foo',): 'bar'}, self.to_dict(node, None))
711
        # The length should be known:
712
        self.assertEqual(1, len(node))
713
        # serialising the node should serialise the child and the node.
714
        chk_bytes = self.get_chk_bytes()
715
        keys = list(node.serialise(chk_bytes))
716
        child_key = child.serialise(chk_bytes)[0]
717
        self.assertEqual(
718
            [child_key, ('sha1:db23b260c2bf46bf7446c39f91668900a2491610',)],
719
            keys)
720
        # We should be able to access deserialised content.
721
        bytes = self.read_bytes(chk_bytes, keys[1])
722
        node = _deserialise(bytes, keys[1])
723
        self.assertEqual(1, len(node))
724
        self.assertEqual({('foo',): 'bar'}, self.to_dict(node, chk_bytes))
725
        self.assertEqual(3, node._node_width)
726
727
    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.
728
        node = InternalNode('fo')
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
729
        child = LeafNode()
730
        child.set_maximum_size(100)
731
        child.map(None, ("foo",), "bar")
732
        node.add_node("foo", child)
733
        chk_bytes = self.get_chk_bytes()
734
        keys = list(node.serialise(chk_bytes))
735
        self.assertEqual(keys[1], node._key)
736
        node.add_node("fos", child)
737
        self.assertEqual(None, node._key)
738
739
#    def test_add_node_empty_oversized_one_ok_new(self):
740
#    def test_add_node_one_oversized_second_kept_minimum_fan(self):
741
#    def test_add_node_two_oversized_third_kept_minimum_fan(self):
742
#    def test_add_node_one_oversized_second_splits_errors(self):
743
744
    def test_iteritems_empty_new(self):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
745
        node = InternalNode()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
746
        self.assertEqual([], sorted(node.iteritems(None)))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
747
748
    def test_iteritems_two_children(self):
749
        node = InternalNode()
750
        leaf1 = LeafNode()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
751
        leaf1.map(None, ('foo bar',), 'quux')
752
        leaf2 = LeafNode()
753
        leaf2.map(None, ('strange',), 'beast')
3735.2.24 by Robert Collins
test_chk_map tests all passing.
754
        node.add_node("f", leaf1)
755
        node.add_node("s", leaf2)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
756
        self.assertEqual([(('foo bar',), 'quux'), (('strange',), 'beast')],
3735.2.24 by Robert Collins
test_chk_map tests all passing.
757
            sorted(node.iteritems(None)))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
758
759
    def test_iteritems_two_children_partial(self):
760
        node = InternalNode()
3735.2.24 by Robert Collins
test_chk_map tests all passing.
761
        leaf1 = LeafNode()
762
        leaf1.map(None, ('foo bar',), 'quux')
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
763
        leaf2 = LeafNode()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
764
        leaf2.map(None, ('strange',), 'beast')
3735.2.24 by Robert Collins
test_chk_map tests all passing.
765
        node.add_node("f", leaf1)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
766
        # This sets up a path that should not be followed - it will error if
767
        # the code tries to.
3735.2.24 by Robert Collins
test_chk_map tests all passing.
768
        node._items['f'] = None
769
        node.add_node("s", leaf2)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
770
        self.assertEqual([(('strange',), 'beast')],
3735.2.24 by Robert Collins
test_chk_map tests all passing.
771
            sorted(node.iteritems(None, [('strange',), ('weird',)])))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
772
773
    def test_iteritems_partial_empty(self):
774
        node = InternalNode()
775
        self.assertEqual([], sorted(node.iteritems([('missing',)])))
776
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
777
    def test_map_to_new_child_new(self):
778
        chkmap = self._get_map({('k1',):'foo', ('k2',):'bar'}, maximum_size=10)
779
        chkmap._ensure_root()
780
        node = chkmap._root_node
781
        # Ensure test validity: nothing paged in below the root.
782
        self.assertEqual(2,
783
            len([value for value in node._items.values()
784
                if type(value) == tuple]))
785
        # now, mapping to k3 should add a k3 leaf
786
        prefix, nodes = node.map(None, ('k3',), 'quux')
787
        self.assertEqual("k", prefix)
788
        self.assertEqual([("", node)], nodes)
789
        # check new child details
790
        child = node._items['k3']
791
        self.assertIsInstance(child, LeafNode)
792
        self.assertEqual(1, len(child))
793
        self.assertEqual({('k3',): 'quux'}, self.to_dict(child, None))
794
        self.assertEqual(None, child._key)
795
        self.assertEqual(10, child.maximum_size)
796
        self.assertEqual(1, child._key_width)
797
        # Check overall structure:
798
        self.assertEqual(3, len(chkmap))
799
        self.assertEqual({('k1',): 'foo', ('k2',): 'bar', ('k3',): 'quux'},
800
            self.to_dict(chkmap))
801
        # serialising should only serialise the new data - k3 and the internal
802
        # node.
803
        keys = list(node.serialise(chkmap._store))
804
        child_key = child.serialise(chkmap._store)[0]
805
        self.assertEqual([child_key, keys[1]], keys)
806
807
    def test_map_to_child_child_splits_new(self):
808
        chkmap = self._get_map({('k1',):'foo', ('k22',):'bar'}, maximum_size=10)
809
        # Check for the canonical root value for this tree:
810
        self.assertEqual(('sha1:d3f06fc03d8f50845894d8d04cc5a3f47e62948d',),
811
            chkmap._root_node)
812
        chkmap._ensure_root()
813
        node = chkmap._root_node
814
        # Ensure test validity: nothing paged in below the root.
815
        self.assertEqual(2,
816
            len([value for value in node._items.values()
817
                if type(value) == tuple]))
818
        # now, mapping to k23 causes k22 ('k2' in node) to split into k22 and
819
        # k23, which for simplicity in the current implementation generates
820
        # a new internal node between node, and k22/k23.
821
        prefix, nodes = node.map(chkmap._store, ('k23',), 'quux')
822
        self.assertEqual("k", prefix)
823
        self.assertEqual([("", node)], nodes)
824
        # check new child details
825
        child = node._items['k2']
826
        self.assertIsInstance(child, InternalNode)
827
        self.assertEqual(2, len(child))
828
        self.assertEqual({('k22',): 'bar', ('k23',): 'quux'},
829
            self.to_dict(child, None))
830
        self.assertEqual(None, child._key)
831
        self.assertEqual(10, child.maximum_size)
832
        self.assertEqual(1, child._key_width)
833
        self.assertEqual(3, child._node_width)
834
        # Check overall structure:
835
        self.assertEqual(3, len(chkmap))
836
        self.assertEqual({('k1',): 'foo', ('k22',): 'bar', ('k23',): 'quux'},
837
            self.to_dict(chkmap))
838
        # serialising should only serialise the new data - although k22 hasn't
839
        # changed because its a special corner case (splitting on with only one
840
        # key leaves one node unaltered), in general k22 is serialised, so we
841
        # expect k22, k23, the new internal node, and node, to be serialised.
842
        keys = list(node.serialise(chkmap._store))
843
        child_key = child._key
844
        k22_key = child._items['k22']._key
845
        k23_key = child._items['k23']._key
846
        self.assertEqual([k22_key, k23_key, child_key, keys[-1]], keys)
847
        self.assertEqual(('sha1:d68cd97c95e847d3dc58c05537aa5fdcdf2cf5da',),
848
            keys[-1])
849
850
    def test_unmap_k23_from_k1_k22_k23_gives_k1_k22_tree_new(self):
851
        chkmap = self._get_map(
852
            {('k1',):'foo', ('k22',):'bar', ('k23',): 'quux'}, maximum_size=10)
853
        # Check we have the expected tree.
854
        self.assertEqual(('sha1:d68cd97c95e847d3dc58c05537aa5fdcdf2cf5da',),
855
            chkmap._root_node)
856
        chkmap._ensure_root()
857
        node = chkmap._root_node
858
        # unmapping k23 should give us a root, with k1 and k22 as direct
859
        # children.
860
        result = node.unmap(chkmap._store, ('k23',))
861
        # check the pointed-at object within node - k2 should now point at the
862
        # k22 leaf (which should not even have been paged in).
863
        ptr = node._items['k2']
864
        self.assertIsInstance(ptr, tuple)
865
        child = _deserialise(self.read_bytes(chkmap._store, ptr), ptr)
866
        self.assertIsInstance(child, LeafNode)
867
        self.assertEqual(1, len(child))
868
        self.assertEqual({('k22',): 'bar'},
869
            self.to_dict(child, None))
870
        # Check overall structure is instact:
871
        self.assertEqual(2, len(chkmap))
872
        self.assertEqual({('k1',): 'foo', ('k22',): 'bar'},
873
            self.to_dict(chkmap))
874
        # serialising should only serialise the new data - the root node.
875
        keys = list(node.serialise(chkmap._store))
876
        self.assertEqual([keys[-1]], keys)
877
        self.assertEqual(('sha1:d3f06fc03d8f50845894d8d04cc5a3f47e62948d',), keys[-1])
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
878
3735.2.23 by Robert Collins
Test unmapping with one child left but multiple keys.
879
    def test_unmap_k1_from_k1_k22_k23_gives_k22_k23_tree_new(self):
880
        chkmap = self._get_map(
881
            {('k1',):'foo', ('k22',):'bar', ('k23',): 'quux'}, maximum_size=10)
882
        # Check we have the expected tree.
883
        self.assertEqual(('sha1:d68cd97c95e847d3dc58c05537aa5fdcdf2cf5da',),
884
            chkmap._root_node)
885
        chkmap._ensure_root()
886
        node = chkmap._root_node
887
        k2_ptr = node._items['k2']
888
        # unmapping k21 should give us a root, with k22 and k23 as direct
889
        # children, and should not have needed to page in the subtree.
890
        result = node.unmap(chkmap._store, ('k1',))
891
        self.assertEqual(k2_ptr, result)
892
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
893
894
# leaf:
895
# map -> fits - done
896
# map -> doesn't fit - shrink from left till fits
897
#        key data to return: the common prefix, new nodes.
898
899
# unmap -> how to tell if siblings can be combined.
900
#          combing leaf nodes means expanding the prefix to the left; so gather the size of
901
#          all the leaf nodes addressed by expanding the prefix by 1; if any adjacent node
902
#          is an internal node, we know that that is a dense subtree - can't combine.
903
#          otherwise as soon as the sum of serialised values exceeds the split threshold
904
#          we know we can't combine - stop.
905
# unmap -> key return data - space in node, common prefix length? and key count
906
# internal: 
907
# variable length prefixes? -> later start with fixed width to get something going
908
# map -> fits - update pointer to leaf
909
#        return [prefix and node] - seems sound.
910
# map -> doesn't fit - find unique prefix and shift right
911
#        create internal nodes for all the partitions, return list of unique
912
#        prefixes and nodes.
913
# map -> new prefix - create a leaf
914
# unmap -> if child key count 0, remove
915
# unmap -> return space in node, common prefix length? (why?), key count
916
# map:
917
# map, if 1 node returned, use it, otherwise make an internal and populate.
918
# map - unmap - if empty, use empty leafnode (avoids special cases in driver
919
# code)
920
# map inits as empty leafnode.
921
# tools: 
922
# visualiser
923
924
925
# how to handle:
926
# AA, AB, AC, AD, BA
927
# packed internal node - ideal:
928
# AA, AB, AC, AD, BA
929
# single byte fanout - A,B,   AA,AB,AC,AD,     BA
930
# build order's:
931
# BA
932
# AB - split, but we want to end up with AB, BA, in one node, with 
933
# 1-4K get0
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
934
935
936
class TestIterInterestingNodes(TestCaseWithStore):
937
938
    def get_chk_bytes(self):
939
        if getattr(self, '_chk_bytes', None) is None:
940
            self._chk_bytes = super(TestIterInterestingNodes,
941
                                    self).get_chk_bytes()
942
        return self._chk_bytes
943
944
    def get_map_key(self, a_dict):
945
        c_map = self._get_map(a_dict, maximum_size=10,
946
                              chk_bytes=self.get_chk_bytes())
947
        return c_map.key()
948
949
    def assertIterInteresting(self, expected, interesting_keys,
950
                              uninteresting_keys):
951
        """Check the result of iter_interesting_nodes.
952
953
        :param expected: A list of (record_keys, interesting_chk_pages,
954
                                    interesting key value pairs)
955
        """
956
        store = self.get_chk_bytes()
957
        iter_nodes = chk_map.iter_interesting_nodes(store, interesting_keys,
958
                                                    uninteresting_keys)
959
        for count, (exp, act) in enumerate(izip(expected, iter_nodes)):
3735.9.8 by John Arbash Meinel
Simplify the interface.
960
            exp_record_keys, exp_items = exp
961
            records, items = act
3735.9.15 by John Arbash Meinel
Found a bug in iter_interesting_nodes and its test suite.
962
            exp_tuple = (sorted(exp_record_keys), sorted(exp_items))
963
            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
964
            self.assertEqual(exp_tuple, act_tuple)
965
        self.assertEqual(len(expected), count + 1)
966
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
967
    def test_empty_to_one_keys(self):
968
        target = self.get_map_key({('a',): 'content'})
969
        self.assertIterInteresting(
970
            [([target], [(('a',), 'content')])],
971
            [target], [])
972
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
973
    def test_none_to_one_key(self):
974
        basis = self.get_map_key({})
975
        target = self.get_map_key({('a',): 'content'})
976
        self.assertIterInteresting(
3735.9.8 by John Arbash Meinel
Simplify the interface.
977
            [([target], [(('a',), 'content')])],
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
978
            [target], [basis])
979
980
    def test_one_to_none_key(self):
981
        basis = self.get_map_key({('a',): 'content'})
982
        target = self.get_map_key({})
983
        self.assertIterInteresting(
3735.9.8 by John Arbash Meinel
Simplify the interface.
984
            [([target], [])],
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
985
            [target], [basis])
986
987
    def test_common_pages(self):
988
        basis = self.get_map_key({('a',): 'content',
989
                                  ('b',): 'content',
990
                                  ('c',): 'content',
991
                                 })
992
        target = self.get_map_key({('a',): 'content',
993
                                   ('b',): 'other content',
994
                                   ('c',): 'content',
995
                                  })
996
        # Is there a way to get this more directly?
997
        b_key = ('sha1:1d7a45ded01ab77c069350c0e290ae34db5b549b',)
998
        # This should return the root node, and the node for the 'b' key
999
        self.assertIterInteresting(
3735.9.8 by John Arbash Meinel
Simplify the interface.
1000
            [([target], []),
1001
             ([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
1002
            [target], [basis])
1003
1004
    def test_common_sub_page(self):
1005
        basis = self.get_map_key({('aaa',): 'common',
1006
                                  ('c',): 'common',
1007
                                 })
1008
        target = self.get_map_key({('aaa',): 'common',
1009
                                   ('aab',): 'new',
1010
                                   ('c',): 'common',
1011
                                  })
3735.9.6 by John Arbash Meinel
Add a first pass to the interesting search.
1012
        self.assertEqualDiff(
1013
            "'' InternalNode sha1:f88b38806015efe27013260d7402219b7b4d4332\n"
1014
            "  'a' InternalNode sha1:2ce01860338a614b93883a5bbeb89920137ac7ef\n"
1015
            "    'aaa' LeafNode sha1:0b38f800c49ff9ffae346ca6f7e80a4626a5eaca\n"
1016
            "      ('aaa',) 'common'\n"
1017
            "    'aab' LeafNode sha1:10567a3bfcc764fb8d8d9edaa28c0934ada366c5\n"
1018
            "      ('aab',) 'new'\n"
1019
            "  'c' LeafNode sha1:263208de2fce0a8f9db614c1ca39e8f6de8b3802\n"
1020
            "      ('c',) 'common'",
1021
            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
1022
        # The key for the internal aa node
1023
        aa_key = ('sha1:2ce01860338a614b93883a5bbeb89920137ac7ef',)
1024
        # The key for the leaf aab node
1025
        aab_key = ('sha1:10567a3bfcc764fb8d8d9edaa28c0934ada366c5',)
1026
        self.assertIterInteresting(
3735.9.8 by John Arbash Meinel
Simplify the interface.
1027
            [([target], []),
1028
             ([aa_key], []),
1029
             ([aab_key], [(('aab',), 'new')])],
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1030
            [target], [basis])
1031
1032
    def test_multiple_maps(self):
1033
        basis1 = self.get_map_key({('aaa',): 'common',
1034
                                   ('aab',): 'basis1',
1035
                                  })
1036
        basis2 = self.get_map_key({('bbb',): 'common',
1037
                                   ('bbc',): 'basis2',
1038
                                  })
1039
        target1 = self.get_map_key({('aaa',): 'common',
1040
                                    ('aac',): 'target1',
1041
                                    ('bbb',): 'common',
1042
                                   })
1043
        target2 = self.get_map_key({('aaa',): 'common',
1044
                                    ('bba',): 'target2',
1045
                                    ('bbb',): 'common',
1046
                                   })
1047
        # The key for the target1 internal aa node
1048
        aa_key = ('sha1:4c6b1e3e6ecb68fe039d2b00c9091bc037ebf203',)
1049
        # The key for the leaf aac node
1050
        aac_key = ('sha1:8089f6b4f3bd2a058c41be199ef5af0c5b9a0c4f',)
1051
        # 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.
1052
        bb_key = ('sha1:bcc229e6bd1d606ef4630073dc15756e60508365',)
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1053
        # 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.
1054
        bba_key = ('sha1:5ce6a69a21060222bb0a5b48fdbfcca586cc9183',)
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1055
        self.assertIterInteresting(
3735.9.8 by John Arbash Meinel
Simplify the interface.
1056
            [([target1, target2], []),
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1057
             ([aa_key], []),
1058
             ([bb_key], []),
1059
             ([aac_key], [(('aac',), 'target1')]),
1060
             ([bba_key], [(('bba',), 'target2')]),
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1061
            ], [target1, target2], [basis1, basis2])