/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: John Arbash Meinel
  • Date: 2007-03-01 01:56:28 UTC
  • mto: (2255.11.3 dirstate)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: john@arbash-meinel.com-20070301015628-3qy7ndtsui6ya4ix
WorkingTree.unversion() should not raise if unversioning a child and a parent.
Also switch all calls to os.path.join() over to osutils.pathjoin() for correctness
on win32.

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