~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: Robert Collins
  • Date: 2005-10-06 22:15:52 UTC
  • mfrom: (1185.13.2)
  • mto: This revision was merged to the branch mainline in revision 1420.
  • Revision ID: robertc@robertcollins.net-20051006221552-9b15c96fa504e0ad
mergeĀ fromĀ upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 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 of the dirstate functionality being built for WorkingTreeFormat4."""
18
 
 
19
 
import bisect
20
 
import os
21
 
 
22
 
from bzrlib import (
23
 
    dirstate,
24
 
    errors,
25
 
    osutils,
26
 
    )
27
 
from bzrlib.memorytree import MemoryTree
28
 
from bzrlib.tests import TestCase, TestCaseWithTransport
29
 
 
30
 
 
31
 
# TODO:
32
 
# test DirStateRevisionTree : test filtering out of deleted files does not
33
 
#         filter out files called RECYCLED.BIN ;)
34
 
# test 0 parents, 1 parent, 4 parents.
35
 
# test unicode parents, non unicode parents
36
 
# test all change permutations in one and two parents.
37
 
# i.e. file in parent 1, dir in parent 2, symlink in tree.
38
 
# test that renames in the tree result in correct parent paths
39
 
# Test get state from a file, then asking for lines.
40
 
# write a smaller state, and check the file has been truncated.
41
 
# add a entry when its in state deleted
42
 
# revision attribute for root entries.
43
 
# test that utf8 strings are preserved in _row_to_line
44
 
# test parent manipulation
45
 
# test parents that are null in save : i.e. no record in the parent tree for this.
46
 
# todo: _set_data records ghost parents.
47
 
# TESTS to write:
48
 
# general checks for NOT_IN_MEMORY error conditions.
49
 
# set_path_id on a NOT_IN_MEMORY dirstate
50
 
# set_path_id  unicode support
51
 
# set_path_id  setting id of a path not root
52
 
# set_path_id  setting id when there are parents without the id in the parents
53
 
# set_path_id  setting id when there are parents with the id in the parents
54
 
# set_path_id  setting id when state is not in memory
55
 
# set_path_id  setting id when state is in memory unmodified
56
 
# set_path_id  setting id when state is in memory modified
57
 
 
58
 
class TestCaseWithDirState(TestCaseWithTransport):
59
 
    """Helper functions for creating DirState objects with various content."""
60
 
 
61
 
    def create_empty_dirstate(self):
62
 
        """Return a locked but empty dirstate"""
63
 
        state = dirstate.DirState.initialize('dirstate')
64
 
        return state
65
 
 
66
 
    def create_dirstate_with_root(self):
67
 
        """Return a write-locked state with a single root entry."""
68
 
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
69
 
        root_entry_direntry = ('', '', 'a-root-value'), [
70
 
            ('d', '', 0, False, packed_stat),
71
 
            ]
72
 
        dirblocks = []
73
 
        dirblocks.append(('', [root_entry_direntry]))
74
 
        dirblocks.append(('', []))
75
 
        state = self.create_empty_dirstate()
76
 
        try:
77
 
            state._set_data([], dirblocks)
78
 
        except:
79
 
            state.unlock()
80
 
            raise
81
 
        return state
82
 
 
83
 
    def create_dirstate_with_root_and_subdir(self):
84
 
        """Return a locked DirState with a root and a subdir"""
85
 
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
86
 
        subdir_entry = ('', 'subdir', 'subdir-id'), [
87
 
            ('d', '', 0, False, packed_stat),
88
 
            ]
89
 
        state = self.create_dirstate_with_root()
90
 
        try:
91
 
            dirblocks = list(state._dirblocks)
92
 
            dirblocks[1][1].append(subdir_entry)
93
 
            state._set_data([], dirblocks)
94
 
        except:
95
 
            state.unlock()
96
 
            raise
97
 
        return state
98
 
 
99
 
    def create_complex_dirstate(self):
100
 
        """This dirstate contains multiple files and directories.
101
 
 
102
 
         /        a-root-value
103
 
         a/       a-dir
104
 
         b/       b-dir
105
 
         c        c-file
106
 
         d        d-file
107
 
         a/e/     e-dir
108
 
         a/f      f-file
109
 
         b/g      g-file
110
 
         b/h\xc3\xa5  h-\xc3\xa5-file  #This is u'\xe5' encoded into utf-8
111
 
 
112
 
        # Notice that a/e is an empty directory.
113
 
        """
114
 
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
115
 
        null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
116
 
        root_entry = ('', '', 'a-root-value'), [
117
 
            ('d', '', 0, False, packed_stat),
118
 
            ]
119
 
        a_entry = ('', 'a', 'a-dir'), [
120
 
            ('d', '', 0, False, packed_stat),
121
 
            ]
122
 
        b_entry = ('', 'b', 'b-dir'), [
123
 
            ('d', '', 0, False, packed_stat),
124
 
            ]
125
 
        c_entry = ('', 'c', 'c-file'), [
126
 
            ('f', null_sha, 10, False, packed_stat),
127
 
            ]
128
 
        d_entry = ('', 'd', 'd-file'), [
129
 
            ('f', null_sha, 20, False, packed_stat),
130
 
            ]
131
 
        e_entry = ('a', 'e', 'e-dir'), [
132
 
            ('d', '', 0, False, packed_stat),
133
 
            ]
134
 
        f_entry = ('a', 'f', 'f-file'), [
135
 
            ('f', null_sha, 30, False, packed_stat),
136
 
            ]
137
 
        g_entry = ('b', 'g', 'g-file'), [
138
 
            ('f', null_sha, 30, False, packed_stat),
139
 
            ]
140
 
        h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
141
 
            ('f', null_sha, 40, False, packed_stat),
142
 
            ]
143
 
        dirblocks = []
144
 
        dirblocks.append(('', [root_entry]))
145
 
        dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
146
 
        dirblocks.append(('a', [e_entry, f_entry]))
147
 
        dirblocks.append(('b', [g_entry, h_entry]))
148
 
        state = dirstate.DirState.initialize('dirstate')
149
 
        try:
150
 
            state._set_data([], dirblocks)
151
 
        except:
152
 
            state.unlock()
153
 
            raise
154
 
        return state
155
 
 
156
 
    def check_state_with_reopen(self, expected_result, state):
157
 
        """Check that state has current state expected_result.
158
 
 
159
 
        This will check the current state, open the file anew and check it
160
 
        again.
161
 
        This function expects the current state to be locked for writing, and
162
 
        will unlock it before re-opening.
163
 
        This is required because we can't open a lock_read() while something
164
 
        else has a lock_write().
165
 
            write => mutually exclusive lock
166
 
            read => shared lock
167
 
        """
168
 
        # The state should already be write locked, since we just had to do
169
 
        # some operation to get here.
170
 
        assert state._lock_token is not None
171
 
        try:
172
 
            self.assertEqual(expected_result[0],  state.get_parent_ids())
173
 
            # there should be no ghosts in this tree.
174
 
            self.assertEqual([], state.get_ghosts())
175
 
            # there should be one fileid in this tree - the root of the tree.
176
 
            self.assertEqual(expected_result[1], list(state._iter_entries()))
177
 
            state.save()
178
 
        finally:
179
 
            state.unlock()
180
 
        del state # Callers should unlock
181
 
        state = dirstate.DirState.on_file('dirstate')
182
 
        state.lock_read()
183
 
        try:
184
 
            self.assertEqual(expected_result[1], list(state._iter_entries()))
185
 
        finally:
186
 
            state.unlock()
187
 
 
188
 
 
189
 
class TestTreeToDirState(TestCaseWithDirState):
190
 
 
191
 
    def test_empty_to_dirstate(self):
192
 
        """We should be able to create a dirstate for an empty tree."""
193
 
        # There are no files on disk and no parents
194
 
        tree = self.make_branch_and_tree('tree')
195
 
        expected_result = ([], [
196
 
            (('', '', tree.path2id('')), # common details
197
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
198
 
             ])])
199
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
200
 
        self.check_state_with_reopen(expected_result, state)
201
 
 
202
 
    def test_1_parents_empty_to_dirstate(self):
203
 
        # create a parent by doing a commit
204
 
        tree = self.make_branch_and_tree('tree')
205
 
        rev_id = tree.commit('first post').encode('utf8')
206
 
        root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
207
 
        expected_result = ([rev_id], [
208
 
            (('', '', tree.path2id('')), # common details
209
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
210
 
              ('d', '', 0, False, rev_id), # first parent details
211
 
             ])])
212
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
213
 
        self.check_state_with_reopen(expected_result, state)
214
 
 
215
 
    def test_2_parents_empty_to_dirstate(self):
216
 
        # create a parent by doing a commit
217
 
        tree = self.make_branch_and_tree('tree')
218
 
        rev_id = tree.commit('first post')
219
 
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
220
 
        rev_id2 = tree2.commit('second post', allow_pointless=True)
221
 
        tree.merge_from_branch(tree2.branch)
222
 
        expected_result = ([rev_id, rev_id2], [
223
 
            (('', '', tree.path2id('')), # common details
224
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
225
 
              ('d', '', 0, False, rev_id), # first parent details
226
 
              ('d', '', 0, False, rev_id2), # second parent details
227
 
             ])])
228
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
229
 
        self.check_state_with_reopen(expected_result, state)
230
 
 
231
 
    def test_empty_unknowns_are_ignored_to_dirstate(self):
232
 
        """We should be able to create a dirstate for an empty tree."""
233
 
        # There are no files on disk and no parents
234
 
        tree = self.make_branch_and_tree('tree')
235
 
        self.build_tree(['tree/unknown'])
236
 
        expected_result = ([], [
237
 
            (('', '', tree.path2id('')), # common details
238
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
239
 
             ])])
240
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
241
 
        self.check_state_with_reopen(expected_result, state)
242
 
 
243
 
    def get_tree_with_a_file(self):
244
 
        tree = self.make_branch_and_tree('tree')
245
 
        self.build_tree(['tree/a file'])
246
 
        tree.add('a file', 'a file id')
247
 
        return tree
248
 
 
249
 
    def test_non_empty_no_parents_to_dirstate(self):
250
 
        """We should be able to create a dirstate for an empty tree."""
251
 
        # There are files on disk and no parents
252
 
        tree = self.get_tree_with_a_file()
253
 
        expected_result = ([], [
254
 
            (('', '', tree.path2id('')), # common details
255
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
256
 
             ]),
257
 
            (('', 'a file', 'a file id'), # common
258
 
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
259
 
             ]),
260
 
            ])
261
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
262
 
        self.check_state_with_reopen(expected_result, state)
263
 
 
264
 
    def test_1_parents_not_empty_to_dirstate(self):
265
 
        # create a parent by doing a commit
266
 
        tree = self.get_tree_with_a_file()
267
 
        rev_id = tree.commit('first post').encode('utf8')
268
 
        # change the current content to be different this will alter stat, sha
269
 
        # and length:
270
 
        self.build_tree_contents([('tree/a file', 'new content\n')])
271
 
        expected_result = ([rev_id], [
272
 
            (('', '', tree.path2id('')), # common details
273
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
274
 
              ('d', '', 0, False, rev_id), # first parent details
275
 
             ]),
276
 
            (('', 'a file', 'a file id'), # common
277
 
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
278
 
              ('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
279
 
               rev_id), # first parent
280
 
             ]),
281
 
            ])
282
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
283
 
        self.check_state_with_reopen(expected_result, state)
284
 
 
285
 
    def test_2_parents_not_empty_to_dirstate(self):
286
 
        # create a parent by doing a commit
287
 
        tree = self.get_tree_with_a_file()
288
 
        rev_id = tree.commit('first post').encode('utf8')
289
 
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
290
 
        # change the current content to be different this will alter stat, sha
291
 
        # and length:
292
 
        self.build_tree_contents([('tree2/a file', 'merge content\n')])
293
 
        rev_id2 = tree2.commit('second post').encode('utf8')
294
 
        tree.merge_from_branch(tree2.branch)
295
 
        # change the current content to be different this will alter stat, sha
296
 
        # and length again, giving us three distinct values:
297
 
        self.build_tree_contents([('tree/a file', 'new content\n')])
298
 
        expected_result = ([rev_id, rev_id2], [
299
 
            (('', '', tree.path2id('')), # common details
300
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
301
 
              ('d', '', 0, False, rev_id), # first parent details
302
 
              ('d', '', 0, False, rev_id2), # second parent details
303
 
             ]),
304
 
            (('', 'a file', 'a file id'), # common
305
 
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
306
 
              ('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
307
 
               rev_id), # first parent
308
 
              ('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
309
 
               rev_id2), # second parent
310
 
             ]),
311
 
            ])
312
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
313
 
        self.check_state_with_reopen(expected_result, state)
314
 
 
315
 
 
316
 
class TestDirStateOnFile(TestCaseWithDirState):
317
 
 
318
 
    def test_construct_with_path(self):
319
 
        tree = self.make_branch_and_tree('tree')
320
 
        state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
321
 
        # we want to be able to get the lines of the dirstate that we will
322
 
        # write to disk.
323
 
        lines = state.get_lines()
324
 
        state.unlock()
325
 
        self.build_tree_contents([('dirstate', ''.join(lines))])
326
 
        # get a state object
327
 
        # no parents, default tree content
328
 
        expected_result = ([], [
329
 
            (('', '', tree.path2id('')), # common details
330
 
             # current tree details, but new from_tree skips statting, it
331
 
             # uses set_state_from_inventory, and thus depends on the
332
 
             # inventory state.
333
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT),
334
 
             ])
335
 
            ])
336
 
        state = dirstate.DirState.on_file('dirstate')
337
 
        state.lock_write() # check_state_with_reopen will save() and unlock it
338
 
        self.check_state_with_reopen(expected_result, state)
339
 
 
340
 
    def test_can_save_clean_on_file(self):
341
 
        tree = self.make_branch_and_tree('tree')
342
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
343
 
        try:
344
 
            # doing a save should work here as there have been no changes.
345
 
            state.save()
346
 
            # TODO: stat it and check it hasn't changed; may require waiting
347
 
            # for the state accuracy window.
348
 
        finally:
349
 
            state.unlock()
350
 
 
351
 
 
352
 
class TestDirStateInitialize(TestCaseWithDirState):
353
 
 
354
 
    def test_initialize(self):
355
 
        expected_result = ([], [
356
 
            (('', '', 'TREE_ROOT'), # common details
357
 
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
358
 
             ])
359
 
            ])
360
 
        state = dirstate.DirState.initialize('dirstate')
361
 
        try:
362
 
            self.assertIsInstance(state, dirstate.DirState)
363
 
            lines = state.get_lines()
364
 
            self.assertFileEqual(''.join(state.get_lines()),
365
 
                'dirstate')
366
 
            self.check_state_with_reopen(expected_result, state)
367
 
        except:
368
 
            state.unlock()
369
 
            raise
370
 
 
371
 
 
372
 
class TestDirStateManipulations(TestCaseWithDirState):
373
 
 
374
 
    def test_set_state_from_inventory_no_content_no_parents(self):
375
 
        # setting the current inventory is a slow but important api to support.
376
 
        tree1 = self.make_branch_and_memory_tree('tree1')
377
 
        tree1.lock_write()
378
 
        try:
379
 
            tree1.add('')
380
 
            revid1 = tree1.commit('foo').encode('utf8')
381
 
            root_id = tree1.inventory.root.file_id
382
 
            inv = tree1.inventory
383
 
        finally:
384
 
            tree1.unlock()
385
 
        expected_result = [], [
386
 
            (('', '', root_id), [
387
 
             ('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
388
 
        state = dirstate.DirState.initialize('dirstate')
389
 
        try:
390
 
            state.set_state_from_inventory(inv)
391
 
            self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
392
 
                             state._header_state)
393
 
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
394
 
                             state._dirblock_state)
395
 
        except:
396
 
            state.unlock()
397
 
            raise
398
 
        else:
399
 
            # This will unlock it
400
 
            self.check_state_with_reopen(expected_result, state)
401
 
 
402
 
    def test_set_path_id_no_parents(self):
403
 
        """The id of a path can be changed trivally with no parents."""
404
 
        state = dirstate.DirState.initialize('dirstate')
405
 
        try:
406
 
            # check precondition to be sure the state does change appropriately.
407
 
            self.assertEqual(
408
 
                [(('', '', 'TREE_ROOT'), [('d', '', 0, False,
409
 
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
410
 
                list(state._iter_entries()))
411
 
            state.set_path_id('', 'foobarbaz')
412
 
            expected_rows = [
413
 
                (('', '', 'foobarbaz'), [('d', '', 0, False,
414
 
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
415
 
            self.assertEqual(expected_rows, list(state._iter_entries()))
416
 
            # should work across save too
417
 
            state.save()
418
 
        finally:
419
 
            state.unlock()
420
 
        state = dirstate.DirState.on_file('dirstate')
421
 
        state.lock_read()
422
 
        try:
423
 
            self.assertEqual(expected_rows, list(state._iter_entries()))
424
 
        finally:
425
 
            state.unlock()
426
 
 
427
 
    def test_set_parent_trees_no_content(self):
428
 
        # set_parent_trees is a slow but important api to support.
429
 
        tree1 = self.make_branch_and_memory_tree('tree1')
430
 
        tree1.lock_write()
431
 
        try:
432
 
            tree1.add('')
433
 
            revid1 = tree1.commit('foo')
434
 
        finally:
435
 
            tree1.unlock()
436
 
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
437
 
        tree2 = MemoryTree.create_on_branch(branch2)
438
 
        tree2.lock_write()
439
 
        try:
440
 
            revid2 = tree2.commit('foo')
441
 
            root_id = tree2.inventory.root.file_id
442
 
        finally:
443
 
            tree2.unlock()
444
 
        state = dirstate.DirState.initialize('dirstate')
445
 
        try:
446
 
            state.set_path_id('', root_id)
447
 
            state.set_parent_trees(
448
 
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
449
 
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
450
 
                 ('ghost-rev', None)),
451
 
                ['ghost-rev'])
452
 
            # check we can reopen and use the dirstate after setting parent
453
 
            # trees.
454
 
            state.save()
455
 
        finally:
456
 
            state.unlock()
457
 
        state = dirstate.DirState.on_file('dirstate')
458
 
        state.lock_write()
459
 
        try:
460
 
            self.assertEqual([revid1, revid2, 'ghost-rev'],
461
 
                             state.get_parent_ids())
462
 
            # iterating the entire state ensures that the state is parsable.
463
 
            list(state._iter_entries())
464
 
            # be sure that it sets not appends - change it
465
 
            state.set_parent_trees(
466
 
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
467
 
                 ('ghost-rev', None)),
468
 
                ['ghost-rev'])
469
 
            # and now put it back.
470
 
            state.set_parent_trees(
471
 
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
472
 
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
473
 
                 ('ghost-rev', tree2.branch.repository.revision_tree(None))),
474
 
                ['ghost-rev'])
475
 
            self.assertEqual([revid1, revid2, 'ghost-rev'],
476
 
                             state.get_parent_ids())
477
 
            # the ghost should be recorded as such by set_parent_trees.
478
 
            self.assertEqual(['ghost-rev'], state.get_ghosts())
479
 
            self.assertEqual(
480
 
                [(('', '', root_id), [
481
 
                  ('d', '', 0, False, dirstate.DirState.NULLSTAT),
482
 
                  ('d', '', 0, False, revid1),
483
 
                  ('d', '', 0, False, revid2)
484
 
                  ])],
485
 
                list(state._iter_entries()))
486
 
        finally:
487
 
            state.unlock()
488
 
 
489
 
    def test_set_parent_trees_file_missing_from_tree(self):
490
 
        # Adding a parent tree may reference files not in the current state.
491
 
        # they should get listed just once by id, even if they are in two
492
 
        # separate trees.
493
 
        # set_parent_trees is a slow but important api to support.
494
 
        tree1 = self.make_branch_and_memory_tree('tree1')
495
 
        tree1.lock_write()
496
 
        try:
497
 
            tree1.add('')
498
 
            tree1.add(['a file'], ['file-id'], ['file'])
499
 
            tree1.put_file_bytes_non_atomic('file-id', 'file-content')
500
 
            revid1 = tree1.commit('foo')
501
 
        finally:
502
 
            tree1.unlock()
503
 
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
504
 
        tree2 = MemoryTree.create_on_branch(branch2)
505
 
        tree2.lock_write()
506
 
        try:
507
 
            tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
508
 
            revid2 = tree2.commit('foo')
509
 
            root_id = tree2.inventory.root.file_id
510
 
        finally:
511
 
            tree2.unlock()
512
 
        # check the layout in memory
513
 
        expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
514
 
            (('', '', root_id), [
515
 
             ('d', '', 0, False, dirstate.DirState.NULLSTAT),
516
 
             ('d', '', 0, False, revid1.encode('utf8')),
517
 
             ('d', '', 0, False, revid2.encode('utf8'))
518
 
             ]),
519
 
            (('', 'a file', 'file-id'), [
520
 
             ('a', '', 0, False, ''),
521
 
             ('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
522
 
              revid1.encode('utf8')),
523
 
             ('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
524
 
              revid2.encode('utf8'))
525
 
             ])
526
 
            ]
527
 
        state = dirstate.DirState.initialize('dirstate')
528
 
        try:
529
 
            state.set_path_id('', root_id)
530
 
            state.set_parent_trees(
531
 
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
532
 
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
533
 
                 ), [])
534
 
        except:
535
 
            state.unlock()
536
 
            raise
537
 
        else:
538
 
            # check_state_with_reopen will unlock
539
 
            self.check_state_with_reopen(expected_result, state)
540
 
 
541
 
    ### add a path via _set_data - so we dont need delta work, just
542
 
    # raw data in, and ensure that it comes out via get_lines happily.
543
 
 
544
 
    def test_add_path_to_root_no_parents_all_data(self):
545
 
        # The most trivial addition of a path is when there are no parents and
546
 
        # its in the root and all data about the file is supplied
547
 
        self.build_tree(['a file'])
548
 
        stat = os.lstat('a file')
549
 
        # the 1*20 is the sha1 pretend value.
550
 
        state = dirstate.DirState.initialize('dirstate')
551
 
        expected_entries = [
552
 
            (('', '', 'TREE_ROOT'), [
553
 
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
554
 
             ]),
555
 
            (('', 'a file', 'a file id'), [
556
 
             ('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
557
 
             ]),
558
 
            ]
559
 
        try:
560
 
            state.add('a file', 'a file id', 'file', stat, '1'*20)
561
 
            # having added it, it should be in the output of iter_entries.
562
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
563
 
            # saving and reloading should not affect this.
564
 
            state.save()
565
 
        finally:
566
 
            state.unlock()
567
 
        state = dirstate.DirState.on_file('dirstate')
568
 
        state.lock_read()
569
 
        try:
570
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
571
 
        finally:
572
 
            state.unlock()
573
 
 
574
 
    def test_add_path_to_unversioned_directory(self):
575
 
        """Adding a path to an unversioned directory should error.
576
 
 
577
 
        This is a duplicate of TestWorkingTree.test_add_in_unversioned,
578
 
        once dirstate is stable and if it is merged with WorkingTree3, consider
579
 
        removing this copy of the test.
580
 
        """
581
 
        self.build_tree(['unversioned/', 'unversioned/a file'])
582
 
        state = dirstate.DirState.initialize('dirstate')
583
 
        try:
584
 
            self.assertRaises(errors.NotVersionedError, state.add,
585
 
                'unversioned/a file', 'a file id', 'file', None, None)
586
 
        finally:
587
 
            state.unlock()
588
 
 
589
 
    def test_add_directory_to_root_no_parents_all_data(self):
590
 
        # The most trivial addition of a dir is when there are no parents and
591
 
        # its in the root and all data about the file is supplied
592
 
        self.build_tree(['a dir/'])
593
 
        stat = os.lstat('a dir')
594
 
        expected_entries = [
595
 
            (('', '', 'TREE_ROOT'), [
596
 
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
597
 
             ]),
598
 
            (('', 'a dir', 'a dir id'), [
599
 
             ('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
600
 
             ]),
601
 
            ]
602
 
        state = dirstate.DirState.initialize('dirstate')
603
 
        try:
604
 
            state.add('a dir', 'a dir id', 'directory', stat, None)
605
 
            # having added it, it should be in the output of iter_entries.
606
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
607
 
            # saving and reloading should not affect this.
608
 
            state.save()
609
 
        finally:
610
 
            state.unlock()
611
 
        state = dirstate.DirState.on_file('dirstate')
612
 
        state.lock_read()
613
 
        try:
614
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
615
 
        finally:
616
 
            state.unlock()
617
 
 
618
 
    def test_add_symlink_to_root_no_parents_all_data(self):
619
 
        # The most trivial addition of a symlink when there are no parents and
620
 
        # its in the root and all data about the file is supplied
621
 
        ## TODO: windows: dont fail this test. Also, how are symlinks meant to
622
 
        # be represented on windows.
623
 
        os.symlink('target', 'a link')
624
 
        stat = os.lstat('a link')
625
 
        expected_entries = [
626
 
            (('', '', 'TREE_ROOT'), [
627
 
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
628
 
             ]),
629
 
            (('', 'a link', 'a link id'), [
630
 
             ('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
631
 
             ]),
632
 
            ]
633
 
        state = dirstate.DirState.initialize('dirstate')
634
 
        try:
635
 
            state.add('a link', 'a link id', 'symlink', stat, 'target')
636
 
            # having added it, it should be in the output of iter_entries.
637
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
638
 
            # saving and reloading should not affect this.
639
 
            state.save()
640
 
        finally:
641
 
            state.unlock()
642
 
        state = dirstate.DirState.on_file('dirstate')
643
 
        state.lock_read()
644
 
        try:
645
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
646
 
        finally:
647
 
            state.unlock()
648
 
 
649
 
    def test_add_directory_and_child_no_parents_all_data(self):
650
 
        # after adding a directory, we should be able to add children to it.
651
 
        self.build_tree(['a dir/', 'a dir/a file'])
652
 
        dirstat = os.lstat('a dir')
653
 
        filestat = os.lstat('a dir/a file')
654
 
        expected_entries = [
655
 
            (('', '', 'TREE_ROOT'), [
656
 
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
657
 
             ]),
658
 
            (('', 'a dir', 'a dir id'), [
659
 
             ('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
660
 
             ]),
661
 
            (('a dir', 'a file', 'a file id'), [
662
 
             ('f', '1'*20, 25, False,
663
 
              dirstate.pack_stat(filestat)), # current tree details
664
 
             ]),
665
 
            ]
666
 
        state = dirstate.DirState.initialize('dirstate')
667
 
        try:
668
 
            state.add('a dir', 'a dir id', 'directory', dirstat, None)
669
 
            state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
670
 
            # added it, it should be in the output of iter_entries.
671
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
672
 
            # saving and reloading should not affect this.
673
 
            state.save()
674
 
        finally:
675
 
            state.unlock()
676
 
        state = dirstate.DirState.on_file('dirstate')
677
 
        state.lock_read()
678
 
        try:
679
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
680
 
        finally:
681
 
            state.unlock()
682
 
 
683
 
 
684
 
class TestGetLines(TestCaseWithDirState):
685
 
 
686
 
    def test_get_line_with_2_rows(self):
687
 
        state = self.create_dirstate_with_root_and_subdir()
688
 
        try:
689
 
            self.assertEqual(['#bazaar dirstate flat format 3\n',
690
 
                'adler32: -1327947603\n',
691
 
                'num_entries: 2\n',
692
 
                '0\x00\n\x00'
693
 
                '0\x00\n\x00'
694
 
                '\x00\x00a-root-value\x00'
695
 
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
696
 
                '\x00subdir\x00subdir-id\x00'
697
 
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
698
 
                ], state.get_lines())
699
 
        finally:
700
 
            state.unlock()
701
 
 
702
 
    def test_entry_to_line(self):
703
 
        state = self.create_dirstate_with_root()
704
 
        try:
705
 
            self.assertEqual(
706
 
                '\x00\x00a-root-value\x00d\x00\x000\x00n'
707
 
                '\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
708
 
                state._entry_to_line(state._dirblocks[0][1][0]))
709
 
        finally:
710
 
            state.unlock()
711
 
 
712
 
    def test_entry_to_line_with_parent(self):
713
 
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
714
 
        root_entry = ('', '', 'a-root-value'), [
715
 
            ('d', '', 0, False, packed_stat), # current tree details
716
 
             # first: a pointer to the current location
717
 
            ('a', 'dirname/basename', 0, False, ''),
718
 
            ]
719
 
        state = dirstate.DirState.initialize('dirstate')
720
 
        try:
721
 
            self.assertEqual(
722
 
                '\x00\x00a-root-value\x00'
723
 
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
724
 
                'a\x00dirname/basename\x000\x00n\x00',
725
 
                state._entry_to_line(root_entry))
726
 
        finally:
727
 
            state.unlock()
728
 
 
729
 
    def test_entry_to_line_with_two_parents_at_different_paths(self):
730
 
        # / in the tree, at / in one parent and /dirname/basename in the other.
731
 
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
732
 
        root_entry = ('', '', 'a-root-value'), [
733
 
            ('d', '', 0, False, packed_stat), # current tree details
734
 
            ('d', '', 0, False, 'rev_id'), # first parent details
735
 
             # second: a pointer to the current location
736
 
            ('a', 'dirname/basename', 0, False, ''),
737
 
            ]
738
 
        state = dirstate.DirState.initialize('dirstate')
739
 
        try:
740
 
            self.assertEqual(
741
 
                '\x00\x00a-root-value\x00'
742
 
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
743
 
                'd\x00\x000\x00n\x00rev_id\x00'
744
 
                'a\x00dirname/basename\x000\x00n\x00',
745
 
                state._entry_to_line(root_entry))
746
 
        finally:
747
 
            state.unlock()
748
 
 
749
 
    def test_iter_entries(self):
750
 
        # we should be able to iterate the dirstate entries from end to end
751
 
        # this is for get_lines to be easy to read.
752
 
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
753
 
        dirblocks = []
754
 
        root_entries = [(('', '', 'a-root-value'), [
755
 
            ('d', '', 0, False, packed_stat), # current tree details
756
 
            ])]
757
 
        dirblocks.append(('', root_entries))
758
 
        # add two files in the root
759
 
        subdir_entry = ('', 'subdir', 'subdir-id'), [
760
 
            ('d', '', 0, False, packed_stat), # current tree details
761
 
            ]
762
 
        afile_entry = ('', 'afile', 'afile-id'), [
763
 
            ('f', 'sha1value', 34, False, packed_stat), # current tree details
764
 
            ]
765
 
        dirblocks.append(('', [subdir_entry, afile_entry]))
766
 
        # and one in subdir
767
 
        file_entry2 = ('subdir', '2file', '2file-id'), [
768
 
            ('f', 'sha1value', 23, False, packed_stat), # current tree details
769
 
            ]
770
 
        dirblocks.append(('subdir', [file_entry2]))
771
 
        state = dirstate.DirState.initialize('dirstate')
772
 
        try:
773
 
            state._set_data([], dirblocks)
774
 
            expected_entries = [root_entries[0], subdir_entry, afile_entry,
775
 
                                file_entry2]
776
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
777
 
        finally:
778
 
            state.unlock()
779
 
 
780
 
 
781
 
class TestGetBlockRowIndex(TestCaseWithDirState):
782
 
 
783
 
    def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
784
 
        file_present, state, dirname, basename, tree_index):
785
 
        self.assertEqual((block_index, row_index, dir_present, file_present),
786
 
            state._get_block_entry_index(dirname, basename, tree_index))
787
 
        if dir_present:
788
 
            block = state._dirblocks[block_index]
789
 
            self.assertEqual(dirname, block[0])
790
 
        if dir_present and file_present:
791
 
            row = state._dirblocks[block_index][1][row_index]
792
 
            self.assertEqual(dirname, row[0][0])
793
 
            self.assertEqual(basename, row[0][1])
794
 
 
795
 
    def test_simple_structure(self):
796
 
        state = self.create_dirstate_with_root_and_subdir()
797
 
        self.addCleanup(state.unlock)
798
 
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
799
 
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
800
 
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
801
 
        self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
802
 
        self.assertBlockRowIndexEqual(2, 0, False, False, state,
803
 
                                      'subdir', 'foo', 0)
804
 
 
805
 
    def test_complex_structure_exists(self):
806
 
        state = self.create_complex_dirstate()
807
 
        self.addCleanup(state.unlock)
808
 
        # Make sure we can find everything that exists
809
 
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
810
 
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
811
 
        self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
812
 
        self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
813
 
        self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
814
 
        self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
815
 
        self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
816
 
        self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
817
 
        self.assertBlockRowIndexEqual(3, 1, True, True, state,
818
 
                                      'b', 'h\xc3\xa5', 0)
819
 
 
820
 
    def test_complex_structure_missing(self):
821
 
        state = self.create_complex_dirstate()
822
 
        self.addCleanup(state.unlock)
823
 
        # Make sure things would be inserted in the right locations
824
 
        # '_' comes before 'a'
825
 
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
826
 
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
827
 
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
828
 
        self.assertBlockRowIndexEqual(1, 4, True, False, state,
829
 
                                      '', 'h\xc3\xa5', 0)
830
 
        self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
831
 
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
832
 
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
833
 
        # This would be inserted between a/ and b/
834
 
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
835
 
        # Put at the end
836
 
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
837
 
 
838
 
 
839
 
class TestGetEntry(TestCaseWithDirState):
840
 
 
841
 
    def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
842
 
        """Check that the right entry is returned for a request to getEntry."""
843
 
        entry = state._get_entry(index, path_utf8=path)
844
 
        if file_id is None:
845
 
            self.assertEqual((None, None), entry)
846
 
        else:
847
 
            cur = entry[0]
848
 
            self.assertEqual((dirname, basename, file_id), cur[:3])
849
 
 
850
 
    def test_simple_structure(self):
851
 
        state = self.create_dirstate_with_root_and_subdir()
852
 
        self.addCleanup(state.unlock)
853
 
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
854
 
        self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
855
 
        self.assertEntryEqual(None, None, None, state, 'missing', 0)
856
 
        self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
857
 
        self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
858
 
 
859
 
    def test_complex_structure_exists(self):
860
 
        state = self.create_complex_dirstate()
861
 
        self.addCleanup(state.unlock)
862
 
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
863
 
        self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
864
 
        self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
865
 
        self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
866
 
        self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
867
 
        self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
868
 
        self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
869
 
        self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
870
 
        self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
871
 
                              'b/h\xc3\xa5', 0)
872
 
 
873
 
    def test_complex_structure_missing(self):
874
 
        state = self.create_complex_dirstate()
875
 
        self.addCleanup(state.unlock)
876
 
        self.assertEntryEqual(None, None, None, state, '_', 0)
877
 
        self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
878
 
        self.assertEntryEqual(None, None, None, state, 'a/b', 0)
879
 
        self.assertEntryEqual(None, None, None, state, 'c/d', 0)
880
 
 
881
 
    def test_get_entry_uninitialized(self):
882
 
        """Calling get_entry will load data if it needs to"""
883
 
        state = self.create_dirstate_with_root()
884
 
        try:
885
 
            state.save()
886
 
        finally:
887
 
            state.unlock()
888
 
        del state
889
 
        state = dirstate.DirState.on_file('dirstate')
890
 
        state.lock_read()
891
 
        try:
892
 
            self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
893
 
                             state._header_state)
894
 
            self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
895
 
                             state._dirblock_state)
896
 
            self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
897
 
        finally:
898
 
            state.unlock()
899
 
 
900
 
 
901
 
class TestDirstateSortOrder(TestCaseWithTransport):
902
 
    """Test that DirState adds entries in the right order."""
903
 
 
904
 
    def test_add_sorting(self):
905
 
        """Add entries in lexicographical order, we get path sorted order.
906
 
 
907
 
        This tests it to a depth of 4, to make sure we don't just get it right
908
 
        at a single depth. 'a/a' should come before 'a-a', even though it
909
 
        doesn't lexicographically.
910
 
        """
911
 
        dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
912
 
                'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
913
 
               ]
914
 
        null_sha = ''
915
 
        state = dirstate.DirState.initialize('dirstate')
916
 
        self.addCleanup(state.unlock)
917
 
 
918
 
        fake_stat = os.stat('dirstate')
919
 
        for d in dirs:
920
 
            d_id = d.replace('/', '_')+'-id'
921
 
            file_path = d + '/f'
922
 
            file_id = file_path.replace('/', '_')+'-id'
923
 
            state.add(d, d_id, 'directory', fake_stat, null_sha)
924
 
            state.add(file_path, file_id, 'file', fake_stat, null_sha)
925
 
 
926
 
        expected = ['', '', 'a',
927
 
                'a/a', 'a/a/a', 'a/a/a/a',
928
 
                'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
929
 
               ]
930
 
        split = lambda p:p.split('/')
931
 
        self.assertEqual(sorted(expected, key=split), expected)
932
 
        dirblock_names = [d[0] for d in state._dirblocks]
933
 
        self.assertEqual(expected, dirblock_names)
934
 
 
935
 
    def test_set_parent_trees_correct_order(self):
936
 
        """After calling set_parent_trees() we should maintain the order."""
937
 
        dirs = ['a', 'a-a', 'a/a']
938
 
        null_sha = ''
939
 
        state = dirstate.DirState.initialize('dirstate')
940
 
        self.addCleanup(state.unlock)
941
 
 
942
 
        fake_stat = os.stat('dirstate')
943
 
        for d in dirs:
944
 
            d_id = d.replace('/', '_')+'-id'
945
 
            file_path = d + '/f'
946
 
            file_id = file_path.replace('/', '_')+'-id'
947
 
            state.add(d, d_id, 'directory', fake_stat, null_sha)
948
 
            state.add(file_path, file_id, 'file', fake_stat, null_sha)
949
 
 
950
 
        expected = ['', '', 'a', 'a/a', 'a-a']
951
 
        dirblock_names = [d[0] for d in state._dirblocks]
952
 
        self.assertEqual(expected, dirblock_names)
953
 
 
954
 
        # *really* cheesy way to just get an empty tree
955
 
        repo = self.make_repository('repo')
956
 
        empty_tree = repo.revision_tree(None)
957
 
        state.set_parent_trees([('null:', empty_tree)], [])
958
 
 
959
 
        dirblock_names = [d[0] for d in state._dirblocks]
960
 
        self.assertEqual(expected, dirblock_names)
961
 
 
962
 
 
963
 
class TestBisect(TestCaseWithTransport):
964
 
    """Test the ability to bisect into the disk format."""
965
 
 
966
 
    def create_basic_dirstate(self):
967
 
        """Create a dirstate with a few files and directories.
968
 
 
969
 
            a
970
 
            b/
971
 
              c
972
 
              d/
973
 
                e
974
 
            f
975
 
        """
976
 
        tree = self.make_branch_and_tree('tree')
977
 
        paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
978
 
        file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
979
 
        self.build_tree(['tree/' + p for p in paths])
980
 
        tree.set_root_id('TREE_ROOT')
981
 
        tree.add([p.rstrip('/') for p in paths], file_ids)
982
 
        tree.commit('initial', rev_id='rev-1')
983
 
        revision_id = 'rev-1'
984
 
        # a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
985
 
        t = self.get_transport().clone('tree')
986
 
        a_text = t.get_bytes('a')
987
 
        a_sha = osutils.sha_string(a_text)
988
 
        a_len = len(a_text)
989
 
        # b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
990
 
        # c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
991
 
        c_text = t.get_bytes('b/c')
992
 
        c_sha = osutils.sha_string(c_text)
993
 
        c_len = len(c_text)
994
 
        # d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
995
 
        # e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
996
 
        e_text = t.get_bytes('b/d/e')
997
 
        e_sha = osutils.sha_string(e_text)
998
 
        e_len = len(e_text)
999
 
        # f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1000
 
        f_text = t.get_bytes('f')
1001
 
        f_sha = osutils.sha_string(f_text)
1002
 
        f_len = len(f_text)
1003
 
        null_stat = dirstate.DirState.NULLSTAT
1004
 
        expected = {
1005
 
            '':(('', '', 'TREE_ROOT'), [
1006
 
                  ('d', '', 0, False, null_stat),
1007
 
                  ('d', '', 0, False, revision_id),
1008
 
                ]),
1009
 
            'a':(('', 'a', 'a-id'), [
1010
 
                   ('f', '', 0, False, null_stat),
1011
 
                   ('f', a_sha, a_len, False, revision_id),
1012
 
                 ]),
1013
 
            'b':(('', 'b', 'b-id'), [
1014
 
                  ('d', '', 0, False, null_stat),
1015
 
                  ('d', '', 0, False, revision_id),
1016
 
                 ]),
1017
 
            'b/c':(('b', 'c', 'c-id'), [
1018
 
                    ('f', '', 0, False, null_stat),
1019
 
                    ('f', c_sha, c_len, False, revision_id),
1020
 
                   ]),
1021
 
            'b/d':(('b', 'd', 'd-id'), [
1022
 
                    ('d', '', 0, False, null_stat),
1023
 
                    ('d', '', 0, False, revision_id),
1024
 
                   ]),
1025
 
            'b/d/e':(('b/d', 'e', 'e-id'), [
1026
 
                      ('f', '', 0, False, null_stat),
1027
 
                      ('f', e_sha, e_len, False, revision_id),
1028
 
                     ]),
1029
 
            'f':(('', 'f', 'f-id'), [
1030
 
                  ('f', '', 0, False, null_stat),
1031
 
                  ('f', f_sha, f_len, False, revision_id),
1032
 
                 ]),
1033
 
        }
1034
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
1035
 
        try:
1036
 
            state.save()
1037
 
        finally:
1038
 
            state.unlock()
1039
 
        # Use a different object, to make sure nothing is pre-cached in memory.
1040
 
        state = dirstate.DirState.on_file('dirstate')
1041
 
        state.lock_read()
1042
 
        self.addCleanup(state.unlock)
1043
 
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1044
 
                         state._dirblock_state)
1045
 
        # This is code is only really tested if we actually have to make more
1046
 
        # than one read, so set the page size to something smaller.
1047
 
        # We want it to contain about 2.2 records, so that we have a couple
1048
 
        # records that we can read per attempt
1049
 
        state._bisect_page_size = 200
1050
 
        return tree, state, expected
1051
 
 
1052
 
    def create_duplicated_dirstate(self):
1053
 
        """Create a dirstate with a deleted and added entries.
1054
 
 
1055
 
        This grabs a basic_dirstate, and then removes and re adds every entry
1056
 
        with a new file id.
1057
 
        """
1058
 
        tree, state, expected = self.create_basic_dirstate()
1059
 
        # Now we will just remove and add every file so we get an extra entry
1060
 
        # per entry. Unversion in reverse order so we handle subdirs
1061
 
        tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1062
 
        tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1063
 
                 ['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1064
 
 
1065
 
        # Update the expected dictionary.
1066
 
        for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1067
 
            orig = expected[path]
1068
 
            path2 = path + '2'
1069
 
            # This record was deleted in the current tree
1070
 
            expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1071
 
                                        orig[1][1]])
1072
 
            new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1073
 
            # And didn't exist in the basis tree
1074
 
            expected[path2] = (new_key, [orig[1][0],
1075
 
                                         dirstate.DirState.NULL_PARENT_DETAILS])
1076
 
 
1077
 
        # We will replace the 'dirstate' file underneath 'state', but that is
1078
 
        # okay as lock as we unlock 'state' first.
1079
 
        state.unlock()
1080
 
        try:
1081
 
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1082
 
            try:
1083
 
                new_state.save()
1084
 
            finally:
1085
 
                new_state.unlock()
1086
 
        finally:
1087
 
            # But we need to leave state in a read-lock because we already have
1088
 
            # a cleanup scheduled
1089
 
            state.lock_read()
1090
 
        return tree, state, expected
1091
 
 
1092
 
    def create_renamed_dirstate(self):
1093
 
        """Create a dirstate with a few internal renames.
1094
 
 
1095
 
        This takes the basic dirstate, and moves the paths around.
1096
 
        """
1097
 
        tree, state, expected = self.create_basic_dirstate()
1098
 
        # Rename a file
1099
 
        tree.rename_one('a', 'b/g')
1100
 
        # And a directory
1101
 
        tree.rename_one('b/d', 'h')
1102
 
 
1103
 
        old_a = expected['a']
1104
 
        expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1105
 
        expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1106
 
                                                ('r', 'a', 0, False, '')])
1107
 
        old_d = expected['b/d']
1108
 
        expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1109
 
        expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1110
 
                                             ('r', 'b/d', 0, False, '')])
1111
 
 
1112
 
        old_e = expected['b/d/e']
1113
 
        expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1114
 
                             old_e[1][1]])
1115
 
        expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1116
 
                                                ('r', 'b/d/e', 0, False, '')])
1117
 
 
1118
 
        state.unlock()
1119
 
        try:
1120
 
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1121
 
            try:
1122
 
                new_state.save()
1123
 
            finally:
1124
 
                new_state.unlock()
1125
 
        finally:
1126
 
            state.lock_read()
1127
 
        return tree, state, expected
1128
 
 
1129
 
    def assertBisect(self, expected_map, map_keys, state, paths):
1130
 
        """Assert that bisecting for paths returns the right result.
1131
 
 
1132
 
        :param expected_map: A map from key => entry value
1133
 
        :param map_keys: The keys to expect for each path
1134
 
        :param state: The DirState object.
1135
 
        :param paths: A list of paths, these will automatically be split into
1136
 
                      (dir, name) tuples, and sorted according to how _bisect
1137
 
                      requires.
1138
 
        """
1139
 
        dir_names = sorted(osutils.split(p) for p in paths)
1140
 
        result = state._bisect(dir_names)
1141
 
        # For now, results are just returned in whatever order we read them.
1142
 
        # We could sort by (dir, name, file_id) or something like that, but in
1143
 
        # the end it would still be fairly arbitrary, and we don't want the
1144
 
        # extra overhead if we can avoid it. So sort everything to make sure
1145
 
        # equality is true
1146
 
        assert len(map_keys) == len(dir_names)
1147
 
        expected = {}
1148
 
        for dir_name, keys in zip(dir_names, map_keys):
1149
 
            if keys is None:
1150
 
                # This should not be present in the output
1151
 
                continue
1152
 
            expected[dir_name] = sorted(expected_map[k] for k in keys)
1153
 
 
1154
 
        for dir_name in result:
1155
 
            result[dir_name].sort()
1156
 
 
1157
 
        self.assertEqual(expected, result)
1158
 
 
1159
 
    def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1160
 
        """Assert that bisecting for dirbblocks returns the right result.
1161
 
 
1162
 
        :param expected_map: A map from key => expected values
1163
 
        :param map_keys: A nested list of paths we expect to be returned.
1164
 
            Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1165
 
        :param state: The DirState object.
1166
 
        :param paths: A list of directories
1167
 
        """
1168
 
        result = state._bisect_dirblocks(paths)
1169
 
        assert len(map_keys) == len(paths)
1170
 
 
1171
 
        expected = {}
1172
 
        for path, keys in zip(paths, map_keys):
1173
 
            if keys is None:
1174
 
                # This should not be present in the output
1175
 
                continue
1176
 
            expected[path] = sorted(expected_map[k] for k in keys)
1177
 
        for path in result:
1178
 
            result[path].sort()
1179
 
 
1180
 
        self.assertEqual(expected, result)
1181
 
 
1182
 
    def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1183
 
        """Assert the return value of a recursive bisection.
1184
 
 
1185
 
        :param expected_map: A map from key => entry value
1186
 
        :param map_keys: A list of paths we expect to be returned.
1187
 
            Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1188
 
        :param state: The DirState object.
1189
 
        :param paths: A list of files and directories. It will be broken up
1190
 
            into (dir, name) pairs and sorted before calling _bisect_recursive.
1191
 
        """
1192
 
        expected = {}
1193
 
        for key in map_keys:
1194
 
            entry = expected_map[key]
1195
 
            dir_name_id, trees_info = entry
1196
 
            expected[dir_name_id] = trees_info
1197
 
 
1198
 
        dir_names = sorted(osutils.split(p) for p in paths)
1199
 
        result = state._bisect_recursive(dir_names)
1200
 
 
1201
 
        self.assertEqual(expected, result)
1202
 
 
1203
 
    def test_bisect_each(self):
1204
 
        """Find a single record using bisect."""
1205
 
        tree, state, expected = self.create_basic_dirstate()
1206
 
 
1207
 
        # Bisect should return the rows for the specified files.
1208
 
        self.assertBisect(expected, [['']], state, [''])
1209
 
        self.assertBisect(expected, [['a']], state, ['a'])
1210
 
        self.assertBisect(expected, [['b']], state, ['b'])
1211
 
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
1212
 
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
1213
 
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1214
 
        self.assertBisect(expected, [['f']], state, ['f'])
1215
 
 
1216
 
    def test_bisect_multi(self):
1217
 
        """Bisect can be used to find multiple records at the same time."""
1218
 
        tree, state, expected = self.create_basic_dirstate()
1219
 
        # Bisect should be capable of finding multiple entries at the same time
1220
 
        self.assertBisect(expected, [['a'], ['b'], ['f']],
1221
 
                          state, ['a', 'b', 'f'])
1222
 
        # ('', 'f') sorts before the others
1223
 
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1224
 
                          state, ['b/d', 'b/d/e', 'f'])
1225
 
 
1226
 
    def test_bisect_one_page(self):
1227
 
        """Test bisect when there is only 1 page to read"""
1228
 
        tree, state, expected = self.create_basic_dirstate()
1229
 
        state._bisect_page_size = 5000
1230
 
        self.assertBisect(expected,[['']], state, [''])
1231
 
        self.assertBisect(expected,[['a']], state, ['a'])
1232
 
        self.assertBisect(expected,[['b']], state, ['b'])
1233
 
        self.assertBisect(expected,[['b/c']], state, ['b/c'])
1234
 
        self.assertBisect(expected,[['b/d']], state, ['b/d'])
1235
 
        self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1236
 
        self.assertBisect(expected,[['f']], state, ['f'])
1237
 
        self.assertBisect(expected,[['a'], ['b'], ['f']],
1238
 
                          state, ['a', 'b', 'f'])
1239
 
        # ('', 'f') sorts before the others
1240
 
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1241
 
                          state, ['b/d', 'b/d/e', 'f'])
1242
 
 
1243
 
    def test_bisect_duplicate_paths(self):
1244
 
        """When bisecting for a path, handle multiple entries."""
1245
 
        tree, state, expected = self.create_duplicated_dirstate()
1246
 
 
1247
 
        # Now make sure that both records are properly returned.
1248
 
        self.assertBisect(expected, [['']], state, [''])
1249
 
        self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1250
 
        self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1251
 
        self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1252
 
        self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1253
 
        self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1254
 
                          state, ['b/d/e'])
1255
 
        self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1256
 
 
1257
 
    def test_bisect_page_size_too_small(self):
1258
 
        """If the page size is too small, we will auto increase it."""
1259
 
        tree, state, expected = self.create_basic_dirstate()
1260
 
        state._bisect_page_size = 50
1261
 
        self.assertBisect(expected, [None], state, ['b/e'])
1262
 
        self.assertBisect(expected, [['a']], state, ['a'])
1263
 
        self.assertBisect(expected, [['b']], state, ['b'])
1264
 
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
1265
 
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
1266
 
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1267
 
        self.assertBisect(expected, [['f']], state, ['f'])
1268
 
 
1269
 
    def test_bisect_missing(self):
1270
 
        """Test that bisect return None if it cannot find a path."""
1271
 
        tree, state, expected = self.create_basic_dirstate()
1272
 
        self.assertBisect(expected, [None], state, ['foo'])
1273
 
        self.assertBisect(expected, [None], state, ['b/foo'])
1274
 
        self.assertBisect(expected, [None], state, ['bar/foo'])
1275
 
 
1276
 
        self.assertBisect(expected, [['a'], None, ['b/d']],
1277
 
                          state, ['a', 'foo', 'b/d'])
1278
 
 
1279
 
    def test_bisect_rename(self):
1280
 
        """Check that we find a renamed row."""
1281
 
        tree, state, expected = self.create_renamed_dirstate()
1282
 
 
1283
 
        # Search for the pre and post renamed entries
1284
 
        self.assertBisect(expected, [['a']], state, ['a'])
1285
 
        self.assertBisect(expected, [['b/g']], state, ['b/g'])
1286
 
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
1287
 
        self.assertBisect(expected, [['h']], state, ['h'])
1288
 
 
1289
 
        # What about b/d/e? shouldn't that also get 2 directory entries?
1290
 
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1291
 
        self.assertBisect(expected, [['h/e']], state, ['h/e'])
1292
 
 
1293
 
    def test_bisect_dirblocks(self):
1294
 
        tree, state, expected = self.create_duplicated_dirstate()
1295
 
        self.assertBisectDirBlocks(expected,
1296
 
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1297
 
        self.assertBisectDirBlocks(expected,
1298
 
            [['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1299
 
        self.assertBisectDirBlocks(expected,
1300
 
            [['b/d/e', 'b/d/e2']], state, ['b/d'])
1301
 
        self.assertBisectDirBlocks(expected,
1302
 
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1303
 
             ['b/c', 'b/c2', 'b/d', 'b/d2'],
1304
 
             ['b/d/e', 'b/d/e2'],
1305
 
            ], state, ['', 'b', 'b/d'])
1306
 
 
1307
 
    def test_bisect_dirblocks_missing(self):
1308
 
        tree, state, expected = self.create_basic_dirstate()
1309
 
        self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1310
 
            state, ['b/d', 'b/e'])
1311
 
        # Files don't show up in this search
1312
 
        self.assertBisectDirBlocks(expected, [None], state, ['a'])
1313
 
        self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1314
 
        self.assertBisectDirBlocks(expected, [None], state, ['c'])
1315
 
        self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1316
 
        self.assertBisectDirBlocks(expected, [None], state, ['f'])
1317
 
 
1318
 
    def test_bisect_recursive_each(self):
1319
 
        tree, state, expected = self.create_basic_dirstate()
1320
 
        self.assertBisectRecursive(expected, ['a'], state, ['a'])
1321
 
        self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1322
 
        self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1323
 
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1324
 
                                   state, ['b/d'])
1325
 
        self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1326
 
                                   state, ['b'])
1327
 
        self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1328
 
                                              'b/d', 'b/d/e'],
1329
 
                                   state, [''])
1330
 
 
1331
 
    def test_bisect_recursive_multiple(self):
1332
 
        tree, state, expected = self.create_basic_dirstate()
1333
 
        self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1334
 
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1335
 
                                   state, ['b/d', 'b/d/e'])
1336
 
 
1337
 
    def test_bisect_recursive_missing(self):
1338
 
        tree, state, expected = self.create_basic_dirstate()
1339
 
        self.assertBisectRecursive(expected, [], state, ['d'])
1340
 
        self.assertBisectRecursive(expected, [], state, ['b/e'])
1341
 
        self.assertBisectRecursive(expected, [], state, ['g'])
1342
 
        self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1343
 
 
1344
 
    def test_bisect_recursive_renamed(self):
1345
 
        tree, state, expected = self.create_renamed_dirstate()
1346
 
 
1347
 
        # Looking for either renamed item should find the other
1348
 
        self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1349
 
        self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1350
 
        # Looking in the containing directory should find the rename target,
1351
 
        # and anything in a subdir of the renamed target.
1352
 
        self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1353
 
                                              'b/d/e', 'b/g', 'h', 'h/e'],
1354
 
                                   state, ['b'])
1355
 
 
1356
 
 
1357
 
class TestBisectDirblock(TestCase):
1358
 
    """Test that bisect_dirblock() returns the expected values.
1359
 
 
1360
 
    bisect_dirblock is intended to work like bisect.bisect_left() except it
1361
 
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
1362
 
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
1363
 
    """
1364
 
 
1365
 
    def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1366
 
        """Assert that bisect_split works like bisect_left on the split paths.
1367
 
 
1368
 
        :param dirblocks: A list of (path, [info]) pairs.
1369
 
        :param split_dirblocks: A list of ((split, path), [info]) pairs.
1370
 
        :param path: The path we are indexing.
1371
 
 
1372
 
        All other arguments will be passed along.
1373
 
        """
1374
 
        bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1375
 
                                                 *args, **kwargs)
1376
 
        split_dirblock = (path.split('/'), [])
1377
 
        bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1378
 
                                             *args)
1379
 
        self.assertEqual(bisect_left_idx, bisect_split_idx,
1380
 
                         'bisect_split disagreed. %s != %s'
1381
 
                         ' for key %s'
1382
 
                         % (bisect_left_idx, bisect_split_idx, path)
1383
 
                         )
1384
 
 
1385
 
    def paths_to_dirblocks(self, paths):
1386
 
        """Convert a list of paths into dirblock form.
1387
 
 
1388
 
        Also, ensure that the paths are in proper sorted order.
1389
 
        """
1390
 
        dirblocks = [(path, []) for path in paths]
1391
 
        split_dirblocks = [(path.split('/'), []) for path in paths]
1392
 
        self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1393
 
        return dirblocks, split_dirblocks
1394
 
 
1395
 
    def test_simple(self):
1396
 
        """In the simple case it works just like bisect_left"""
1397
 
        paths = ['', 'a', 'b', 'c', 'd']
1398
 
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1399
 
        for path in paths:
1400
 
            self.assertBisect(dirblocks, split_dirblocks, path)
1401
 
        self.assertBisect(dirblocks, split_dirblocks, '_')
1402
 
        self.assertBisect(dirblocks, split_dirblocks, 'aa')
1403
 
        self.assertBisect(dirblocks, split_dirblocks, 'bb')
1404
 
        self.assertBisect(dirblocks, split_dirblocks, 'cc')
1405
 
        self.assertBisect(dirblocks, split_dirblocks, 'dd')
1406
 
        self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1407
 
        self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1408
 
        self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1409
 
        self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1410
 
 
1411
 
    def test_involved(self):
1412
 
        """This is where bisect_left diverges slightly."""
1413
 
        paths = ['', 'a',
1414
 
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1415
 
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1416
 
                 'a-a', 'a-z',
1417
 
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1418
 
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1419
 
                 'z-a', 'z-z',
1420
 
                ]
1421
 
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1422
 
        for path in paths:
1423
 
            self.assertBisect(dirblocks, split_dirblocks, path)
1424
 
 
1425
 
    def test_involved_cached(self):
1426
 
        """This is where bisect_left diverges slightly."""
1427
 
        paths = ['', 'a',
1428
 
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1429
 
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1430
 
                 'a-a', 'a-z',
1431
 
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1432
 
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1433
 
                 'z-a', 'z-z',
1434
 
                ]
1435
 
        cache = {}
1436
 
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1437
 
        for path in paths:
1438
 
            self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
1439