/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: Martin Pool
  • Date: 2007-03-07 01:31:55 UTC
  • mto: (2321.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: mbp@sourcefrog.net-20070307013155-kxvc6ppleyv8jswg
Add blackbox test that join gives clean error when the repository doesn't support rich roots

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
import time
 
22
 
 
23
from bzrlib import (
 
24
    dirstate,
 
25
    errors,
 
26
    osutils,
 
27
    )
 
28
from bzrlib.memorytree import MemoryTree
 
29
from bzrlib.tests import TestCase, TestCaseWithTransport
 
30
 
 
31
 
 
32
# TODO:
 
33
# test DirStateRevisionTree : test filtering out of deleted files does not
 
34
#         filter out files called RECYCLED.BIN ;)
 
35
# test 0 parents, 1 parent, 4 parents.
 
36
# test unicode parents, non unicode parents
 
37
# test all change permutations in one and two parents.
 
38
# i.e. file in parent 1, dir in parent 2, symlink in tree.
 
39
# test that renames in the tree result in correct parent paths
 
40
# Test get state from a file, then asking for lines.
 
41
# write a smaller state, and check the file has been truncated.
 
42
# add a entry when its in state deleted
 
43
# revision attribute for root entries.
 
44
# test that utf8 strings are preserved in _row_to_line
 
45
# test parent manipulation
 
46
# test parents that are null in save : i.e. no record in the parent tree for this.
 
47
# todo: _set_data records ghost parents.
 
48
# TESTS to write:
 
49
# general checks for NOT_IN_MEMORY error conditions.
 
50
# set_path_id on a NOT_IN_MEMORY dirstate
 
51
# set_path_id  unicode support
 
52
# set_path_id  setting id of a path not root
 
53
# set_path_id  setting id when there are parents without the id in the parents
 
54
# set_path_id  setting id when there are parents with the id in the parents
 
55
# set_path_id  setting id when state is not in memory
 
56
# set_path_id  setting id when state is in memory unmodified
 
57
# set_path_id  setting id when state is in memory modified
 
58
 
 
59
class TestCaseWithDirState(TestCaseWithTransport):
 
60
    """Helper functions for creating DirState objects with various content."""
 
61
 
 
62
    def create_empty_dirstate(self):
 
63
        """Return a locked but empty dirstate"""
 
64
        state = dirstate.DirState.initialize('dirstate')
 
65
        return state
 
66
 
 
67
    def create_dirstate_with_root(self):
 
68
        """Return a write-locked state with a single root entry."""
 
69
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
70
        root_entry_direntry = ('', '', 'a-root-value'), [
 
71
            ('d', '', 0, False, packed_stat),
 
72
            ]
 
73
        dirblocks = []
 
74
        dirblocks.append(('', [root_entry_direntry]))
 
75
        dirblocks.append(('', []))
 
76
        state = self.create_empty_dirstate()
 
77
        try:
 
78
            state._set_data([], dirblocks)
 
79
            state._validate()
 
80
        except:
 
81
            state.unlock()
 
82
            raise
 
83
        return state
 
84
 
 
85
    def create_dirstate_with_root_and_subdir(self):
 
86
        """Return a locked DirState with a root and a subdir"""
 
87
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
88
        subdir_entry = ('', 'subdir', 'subdir-id'), [
 
89
            ('d', '', 0, False, packed_stat),
 
90
            ]
 
91
        state = self.create_dirstate_with_root()
 
92
        try:
 
93
            dirblocks = list(state._dirblocks)
 
94
            dirblocks[1][1].append(subdir_entry)
 
95
            state._set_data([], dirblocks)
 
96
        except:
 
97
            state.unlock()
 
98
            raise
 
99
        return state
 
100
 
 
101
    def create_complex_dirstate(self):
 
102
        """This dirstate contains multiple files and directories.
 
103
 
 
104
         /        a-root-value
 
105
         a/       a-dir
 
106
         b/       b-dir
 
107
         c        c-file
 
108
         d        d-file
 
109
         a/e/     e-dir
 
110
         a/f      f-file
 
111
         b/g      g-file
 
112
         b/h\xc3\xa5  h-\xc3\xa5-file  #This is u'\xe5' encoded into utf-8
 
113
 
 
114
        # Notice that a/e is an empty directory.
 
115
        """
 
116
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
117
        null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
 
118
        root_entry = ('', '', 'a-root-value'), [
 
119
            ('d', '', 0, False, packed_stat),
 
120
            ]
 
121
        a_entry = ('', 'a', 'a-dir'), [
 
122
            ('d', '', 0, False, packed_stat),
 
123
            ]
 
124
        b_entry = ('', 'b', 'b-dir'), [
 
125
            ('d', '', 0, False, packed_stat),
 
126
            ]
 
127
        c_entry = ('', 'c', 'c-file'), [
 
128
            ('f', null_sha, 10, False, packed_stat),
 
129
            ]
 
130
        d_entry = ('', 'd', 'd-file'), [
 
131
            ('f', null_sha, 20, False, packed_stat),
 
132
            ]
 
133
        e_entry = ('a', 'e', 'e-dir'), [
 
134
            ('d', '', 0, False, packed_stat),
 
135
            ]
 
136
        f_entry = ('a', 'f', 'f-file'), [
 
137
            ('f', null_sha, 30, False, packed_stat),
 
138
            ]
 
139
        g_entry = ('b', 'g', 'g-file'), [
 
140
            ('f', null_sha, 30, False, packed_stat),
 
141
            ]
 
142
        h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
 
143
            ('f', null_sha, 40, False, packed_stat),
 
144
            ]
 
145
        dirblocks = []
 
146
        dirblocks.append(('', [root_entry]))
 
147
        dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
 
148
        dirblocks.append(('a', [e_entry, f_entry]))
 
149
        dirblocks.append(('b', [g_entry, h_entry]))
 
150
        state = dirstate.DirState.initialize('dirstate')
 
151
        state._validate()
 
152
        try:
 
153
            state._set_data([], dirblocks)
 
154
        except:
 
155
            state.unlock()
 
156
            raise
 
157
        return state
 
158
 
 
159
    def check_state_with_reopen(self, expected_result, state):
 
160
        """Check that state has current state expected_result.
 
161
 
 
162
        This will check the current state, open the file anew and check it
 
163
        again.
 
164
        This function expects the current state to be locked for writing, and
 
165
        will unlock it before re-opening.
 
166
        This is required because we can't open a lock_read() while something
 
167
        else has a lock_write().
 
168
            write => mutually exclusive lock
 
169
            read => shared lock
 
170
        """
 
171
        # The state should already be write locked, since we just had to do
 
172
        # some operation to get here.
 
173
        assert state._lock_token is not None
 
174
        try:
 
175
            self.assertEqual(expected_result[0],  state.get_parent_ids())
 
176
            # there should be no ghosts in this tree.
 
177
            self.assertEqual([], state.get_ghosts())
 
178
            # there should be one fileid in this tree - the root of the tree.
 
179
            self.assertEqual(expected_result[1], list(state._iter_entries()))
 
180
            state.save()
 
181
        finally:
 
182
            state.unlock()
 
183
        del state # Callers should unlock
 
184
        state = dirstate.DirState.on_file('dirstate')
 
185
        state.lock_read()
 
186
        try:
 
187
            self.assertEqual(expected_result[1], list(state._iter_entries()))
 
188
        finally:
 
189
            state.unlock()
 
190
 
 
191
 
 
192
class TestTreeToDirState(TestCaseWithDirState):
 
193
 
 
194
    def test_empty_to_dirstate(self):
 
195
        """We should be able to create a dirstate for an empty tree."""
 
196
        # There are no files on disk and no parents
 
197
        tree = self.make_branch_and_tree('tree')
 
198
        expected_result = ([], [
 
199
            (('', '', tree.path2id('')), # common details
 
200
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
201
             ])])
 
202
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
203
        state._validate()
 
204
        self.check_state_with_reopen(expected_result, state)
 
205
 
 
206
    def test_1_parents_empty_to_dirstate(self):
 
207
        # create a parent by doing a commit
 
208
        tree = self.make_branch_and_tree('tree')
 
209
        rev_id = tree.commit('first post').encode('utf8')
 
210
        root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
 
211
        expected_result = ([rev_id], [
 
212
            (('', '', tree.path2id('')), # common details
 
213
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
214
              ('d', '', 0, False, rev_id), # first parent details
 
215
             ])])
 
216
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
217
        self.check_state_with_reopen(expected_result, state)
 
218
        state._validate()
 
219
 
 
220
    def test_2_parents_empty_to_dirstate(self):
 
221
        # create a parent by doing a commit
 
222
        tree = self.make_branch_and_tree('tree')
 
223
        rev_id = tree.commit('first post')
 
224
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
225
        rev_id2 = tree2.commit('second post', allow_pointless=True)
 
226
        tree.merge_from_branch(tree2.branch)
 
227
        expected_result = ([rev_id, rev_id2], [
 
228
            (('', '', tree.path2id('')), # common details
 
229
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
230
              ('d', '', 0, False, rev_id), # first parent details
 
231
              ('d', '', 0, False, rev_id2), # second parent details
 
232
             ])])
 
233
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
234
        self.check_state_with_reopen(expected_result, state)
 
235
        state._validate()
 
236
 
 
237
    def test_empty_unknowns_are_ignored_to_dirstate(self):
 
238
        """We should be able to create a dirstate for an empty tree."""
 
239
        # There are no files on disk and no parents
 
240
        tree = self.make_branch_and_tree('tree')
 
241
        self.build_tree(['tree/unknown'])
 
242
        expected_result = ([], [
 
243
            (('', '', tree.path2id('')), # common details
 
244
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
245
             ])])
 
246
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
247
        self.check_state_with_reopen(expected_result, state)
 
248
 
 
249
    def get_tree_with_a_file(self):
 
250
        tree = self.make_branch_and_tree('tree')
 
251
        self.build_tree(['tree/a file'])
 
252
        tree.add('a file', 'a file id')
 
253
        return tree
 
254
 
 
255
    def test_non_empty_no_parents_to_dirstate(self):
 
256
        """We should be able to create a dirstate for an empty tree."""
 
257
        # There are files on disk and no parents
 
258
        tree = self.get_tree_with_a_file()
 
259
        expected_result = ([], [
 
260
            (('', '', tree.path2id('')), # common details
 
261
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
262
             ]),
 
263
            (('', 'a file', 'a file id'), # common
 
264
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
 
265
             ]),
 
266
            ])
 
267
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
268
        self.check_state_with_reopen(expected_result, state)
 
269
 
 
270
    def test_1_parents_not_empty_to_dirstate(self):
 
271
        # create a parent by doing a commit
 
272
        tree = self.get_tree_with_a_file()
 
273
        rev_id = tree.commit('first post').encode('utf8')
 
274
        # change the current content to be different this will alter stat, sha
 
275
        # and length:
 
276
        self.build_tree_contents([('tree/a file', 'new content\n')])
 
277
        expected_result = ([rev_id], [
 
278
            (('', '', tree.path2id('')), # common details
 
279
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
280
              ('d', '', 0, False, rev_id), # first parent details
 
281
             ]),
 
282
            (('', 'a file', 'a file id'), # common
 
283
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
 
284
              ('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
 
285
               rev_id), # first parent
 
286
             ]),
 
287
            ])
 
288
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
289
        self.check_state_with_reopen(expected_result, state)
 
290
 
 
291
    def test_2_parents_not_empty_to_dirstate(self):
 
292
        # create a parent by doing a commit
 
293
        tree = self.get_tree_with_a_file()
 
294
        rev_id = tree.commit('first post').encode('utf8')
 
295
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
296
        # change the current content to be different this will alter stat, sha
 
297
        # and length:
 
298
        self.build_tree_contents([('tree2/a file', 'merge content\n')])
 
299
        rev_id2 = tree2.commit('second post').encode('utf8')
 
300
        tree.merge_from_branch(tree2.branch)
 
301
        # change the current content to be different this will alter stat, sha
 
302
        # and length again, giving us three distinct values:
 
303
        self.build_tree_contents([('tree/a file', 'new content\n')])
 
304
        expected_result = ([rev_id, rev_id2], [
 
305
            (('', '', tree.path2id('')), # common details
 
306
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
307
              ('d', '', 0, False, rev_id), # first parent details
 
308
              ('d', '', 0, False, rev_id2), # second parent details
 
309
             ]),
 
310
            (('', 'a file', 'a file id'), # common
 
311
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
 
312
              ('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
 
313
               rev_id), # first parent
 
314
              ('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
 
315
               rev_id2), # second parent
 
316
             ]),
 
317
            ])
 
318
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
319
        self.check_state_with_reopen(expected_result, state)
 
320
 
 
321
    def test_colliding_fileids(self):
 
322
        # test insertion of parents creating several entries at the same path.
 
323
        # we used to have a bug where they could cause the dirstate to break
 
324
        # its ordering invariants.
 
325
        # create some trees to test from
 
326
        parents = []
 
327
        for i in range(7):
 
328
            tree = self.make_branch_and_tree('tree%d' % i)
 
329
            self.build_tree(['tree%d/name' % i,])
 
330
            tree.add(['name'], ['file-id%d' % i])
 
331
            revision_id = 'revid-%d' % i
 
332
            tree.commit('message', rev_id=revision_id)
 
333
            parents.append((revision_id,
 
334
                tree.branch.repository.revision_tree(revision_id)))
 
335
        # now fold these trees into a dirstate
 
336
        state = dirstate.DirState.initialize('dirstate')
 
337
        try:
 
338
            state.set_parent_trees(parents, [])
 
339
            state._validate()
 
340
        finally:
 
341
            state.unlock()
 
342
 
 
343
 
 
344
class TestDirStateOnFile(TestCaseWithDirState):
 
345
 
 
346
    def test_construct_with_path(self):
 
347
        tree = self.make_branch_and_tree('tree')
 
348
        state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
 
349
        # we want to be able to get the lines of the dirstate that we will
 
350
        # write to disk.
 
351
        lines = state.get_lines()
 
352
        state.unlock()
 
353
        self.build_tree_contents([('dirstate', ''.join(lines))])
 
354
        # get a state object
 
355
        # no parents, default tree content
 
356
        expected_result = ([], [
 
357
            (('', '', tree.path2id('')), # common details
 
358
             # current tree details, but new from_tree skips statting, it
 
359
             # uses set_state_from_inventory, and thus depends on the
 
360
             # inventory state.
 
361
             [('d', '', 0, False, dirstate.DirState.NULLSTAT),
 
362
             ])
 
363
            ])
 
364
        state = dirstate.DirState.on_file('dirstate')
 
365
        state.lock_write() # check_state_with_reopen will save() and unlock it
 
366
        self.check_state_with_reopen(expected_result, state)
 
367
 
 
368
    def test_can_save_clean_on_file(self):
 
369
        tree = self.make_branch_and_tree('tree')
 
370
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
371
        try:
 
372
            # doing a save should work here as there have been no changes.
 
373
            state.save()
 
374
            # TODO: stat it and check it hasn't changed; may require waiting
 
375
            # for the state accuracy window.
 
376
        finally:
 
377
            state.unlock()
 
378
 
 
379
 
 
380
class TestDirStateInitialize(TestCaseWithDirState):
 
381
 
 
382
    def test_initialize(self):
 
383
        expected_result = ([], [
 
384
            (('', '', 'TREE_ROOT'), # common details
 
385
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
386
             ])
 
387
            ])
 
388
        state = dirstate.DirState.initialize('dirstate')
 
389
        try:
 
390
            self.assertIsInstance(state, dirstate.DirState)
 
391
            lines = state.get_lines()
 
392
            self.assertFileEqual(''.join(state.get_lines()),
 
393
                'dirstate')
 
394
            self.check_state_with_reopen(expected_result, state)
 
395
        except:
 
396
            state.unlock()
 
397
            raise
 
398
 
 
399
 
 
400
class TestDirStateManipulations(TestCaseWithDirState):
 
401
 
 
402
    def test_set_state_from_inventory_no_content_no_parents(self):
 
403
        # setting the current inventory is a slow but important api to support.
 
404
        tree1 = self.make_branch_and_memory_tree('tree1')
 
405
        tree1.lock_write()
 
406
        try:
 
407
            tree1.add('')
 
408
            revid1 = tree1.commit('foo').encode('utf8')
 
409
            root_id = tree1.inventory.root.file_id
 
410
            inv = tree1.inventory
 
411
        finally:
 
412
            tree1.unlock()
 
413
        expected_result = [], [
 
414
            (('', '', root_id), [
 
415
             ('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
 
416
        state = dirstate.DirState.initialize('dirstate')
 
417
        try:
 
418
            state.set_state_from_inventory(inv)
 
419
            self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
420
                             state._header_state)
 
421
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
422
                             state._dirblock_state)
 
423
        except:
 
424
            state.unlock()
 
425
            raise
 
426
        else:
 
427
            # This will unlock it
 
428
            self.check_state_with_reopen(expected_result, state)
 
429
 
 
430
    def test_set_path_id_no_parents(self):
 
431
        """The id of a path can be changed trivally with no parents."""
 
432
        state = dirstate.DirState.initialize('dirstate')
 
433
        try:
 
434
            # check precondition to be sure the state does change appropriately.
 
435
            self.assertEqual(
 
436
                [(('', '', 'TREE_ROOT'), [('d', '', 0, False,
 
437
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
 
438
                list(state._iter_entries()))
 
439
            state.set_path_id('', 'foobarbaz')
 
440
            expected_rows = [
 
441
                (('', '', 'foobarbaz'), [('d', '', 0, False,
 
442
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
 
443
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
444
            # should work across save too
 
445
            state.save()
 
446
        finally:
 
447
            state.unlock()
 
448
        state = dirstate.DirState.on_file('dirstate')
 
449
        state._validate()
 
450
        state.lock_read()
 
451
        try:
 
452
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
453
        finally:
 
454
            state.unlock()
 
455
 
 
456
    def test_set_path_id_with_parents(self):
 
457
        """Set the root file id in a dirstate with parents"""
 
458
        mt = self.make_branch_and_tree('mt')
 
459
        # in case the default tree format uses a different root id
 
460
        mt.set_root_id('TREE_ROOT')
 
461
        mt.commit('foo', rev_id='parent-revid')
 
462
        rt = mt.branch.repository.revision_tree('parent-revid')
 
463
        state = dirstate.DirState.initialize('dirstate')
 
464
        state._validate()
 
465
        try:
 
466
            state.set_parent_trees([('parent-revid', rt)], ghosts=[])
 
467
            state.set_path_id('', 'foobarbaz')
 
468
            state._validate()
 
469
            # now see that it is what we expected
 
470
            expected_rows = [
 
471
                (('', '', 'TREE_ROOT'),
 
472
                    [('a', '', 0, False, ''),
 
473
                     ('d', '', 0, False, 'parent-revid'),
 
474
                     ]),
 
475
                (('', '', 'foobarbaz'),
 
476
                    [('d', '', 0, False, ''),
 
477
                     ('a', '', 0, False, ''),
 
478
                     ]),
 
479
                ]
 
480
            state._validate()
 
481
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
482
            # should work across save too
 
483
            state.save()
 
484
        finally:
 
485
            state.unlock()
 
486
        # now flush & check we get the same
 
487
        state = dirstate.DirState.on_file('dirstate')
 
488
        state.lock_read()
 
489
        try:
 
490
            state._validate()
 
491
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
492
        finally:
 
493
            state.unlock()
 
494
        # now change within an existing file-backed state
 
495
        state.lock_write()
 
496
        try:
 
497
            state._validate()
 
498
            state.set_path_id('', 'tree-root-2')
 
499
            state._validate()
 
500
        finally:
 
501
            state.unlock()
 
502
 
 
503
 
 
504
    def test_set_parent_trees_no_content(self):
 
505
        # set_parent_trees is a slow but important api to support.
 
506
        tree1 = self.make_branch_and_memory_tree('tree1')
 
507
        tree1.lock_write()
 
508
        try:
 
509
            tree1.add('')
 
510
            revid1 = tree1.commit('foo')
 
511
        finally:
 
512
            tree1.unlock()
 
513
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
514
        tree2 = MemoryTree.create_on_branch(branch2)
 
515
        tree2.lock_write()
 
516
        try:
 
517
            revid2 = tree2.commit('foo')
 
518
            root_id = tree2.inventory.root.file_id
 
519
        finally:
 
520
            tree2.unlock()
 
521
        state = dirstate.DirState.initialize('dirstate')
 
522
        try:
 
523
            state.set_path_id('', root_id)
 
524
            state.set_parent_trees(
 
525
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
526
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
527
                 ('ghost-rev', None)),
 
528
                ['ghost-rev'])
 
529
            # check we can reopen and use the dirstate after setting parent
 
530
            # trees.
 
531
            state._validate()
 
532
            state.save()
 
533
            state._validate()
 
534
        finally:
 
535
            state.unlock()
 
536
        state = dirstate.DirState.on_file('dirstate')
 
537
        state.lock_write()
 
538
        try:
 
539
            self.assertEqual([revid1, revid2, 'ghost-rev'],
 
540
                             state.get_parent_ids())
 
541
            # iterating the entire state ensures that the state is parsable.
 
542
            list(state._iter_entries())
 
543
            # be sure that it sets not appends - change it
 
544
            state.set_parent_trees(
 
545
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
546
                 ('ghost-rev', None)),
 
547
                ['ghost-rev'])
 
548
            # and now put it back.
 
549
            state.set_parent_trees(
 
550
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
551
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
552
                 ('ghost-rev', tree2.branch.repository.revision_tree(None))),
 
553
                ['ghost-rev'])
 
554
            self.assertEqual([revid1, revid2, 'ghost-rev'],
 
555
                             state.get_parent_ids())
 
556
            # the ghost should be recorded as such by set_parent_trees.
 
557
            self.assertEqual(['ghost-rev'], state.get_ghosts())
 
558
            self.assertEqual(
 
559
                [(('', '', root_id), [
 
560
                  ('d', '', 0, False, dirstate.DirState.NULLSTAT),
 
561
                  ('d', '', 0, False, revid1),
 
562
                  ('d', '', 0, False, revid2)
 
563
                  ])],
 
564
                list(state._iter_entries()))
 
565
        finally:
 
566
            state.unlock()
 
567
 
 
568
    def test_set_parent_trees_file_missing_from_tree(self):
 
569
        # Adding a parent tree may reference files not in the current state.
 
570
        # they should get listed just once by id, even if they are in two
 
571
        # separate trees.
 
572
        # set_parent_trees is a slow but important api to support.
 
573
        tree1 = self.make_branch_and_memory_tree('tree1')
 
574
        tree1.lock_write()
 
575
        try:
 
576
            tree1.add('')
 
577
            tree1.add(['a file'], ['file-id'], ['file'])
 
578
            tree1.put_file_bytes_non_atomic('file-id', 'file-content')
 
579
            revid1 = tree1.commit('foo')
 
580
        finally:
 
581
            tree1.unlock()
 
582
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
583
        tree2 = MemoryTree.create_on_branch(branch2)
 
584
        tree2.lock_write()
 
585
        try:
 
586
            tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
 
587
            revid2 = tree2.commit('foo')
 
588
            root_id = tree2.inventory.root.file_id
 
589
        finally:
 
590
            tree2.unlock()
 
591
        # check the layout in memory
 
592
        expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
 
593
            (('', '', root_id), [
 
594
             ('d', '', 0, False, dirstate.DirState.NULLSTAT),
 
595
             ('d', '', 0, False, revid1.encode('utf8')),
 
596
             ('d', '', 0, False, revid2.encode('utf8'))
 
597
             ]),
 
598
            (('', 'a file', 'file-id'), [
 
599
             ('a', '', 0, False, ''),
 
600
             ('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
 
601
              revid1.encode('utf8')),
 
602
             ('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
 
603
              revid2.encode('utf8'))
 
604
             ])
 
605
            ]
 
606
        state = dirstate.DirState.initialize('dirstate')
 
607
        try:
 
608
            state.set_path_id('', root_id)
 
609
            state.set_parent_trees(
 
610
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
 
611
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
 
612
                 ), [])
 
613
        except:
 
614
            state.unlock()
 
615
            raise
 
616
        else:
 
617
            # check_state_with_reopen will unlock
 
618
            self.check_state_with_reopen(expected_result, state)
 
619
 
 
620
    ### add a path via _set_data - so we dont need delta work, just
 
621
    # raw data in, and ensure that it comes out via get_lines happily.
 
622
 
 
623
    def test_add_path_to_root_no_parents_all_data(self):
 
624
        # The most trivial addition of a path is when there are no parents and
 
625
        # its in the root and all data about the file is supplied
 
626
        self.build_tree(['a file'])
 
627
        stat = os.lstat('a file')
 
628
        # the 1*20 is the sha1 pretend value.
 
629
        state = dirstate.DirState.initialize('dirstate')
 
630
        expected_entries = [
 
631
            (('', '', 'TREE_ROOT'), [
 
632
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
633
             ]),
 
634
            (('', 'a file', 'a file id'), [
 
635
             ('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
 
636
             ]),
 
637
            ]
 
638
        try:
 
639
            state.add('a file', 'a file id', 'file', stat, '1'*20)
 
640
            # having added it, it should be in the output of iter_entries.
 
641
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
642
            # saving and reloading should not affect this.
 
643
            state.save()
 
644
        finally:
 
645
            state.unlock()
 
646
        state = dirstate.DirState.on_file('dirstate')
 
647
        state.lock_read()
 
648
        try:
 
649
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
650
        finally:
 
651
            state.unlock()
 
652
 
 
653
    def test_add_path_to_unversioned_directory(self):
 
654
        """Adding a path to an unversioned directory should error.
 
655
 
 
656
        This is a duplicate of TestWorkingTree.test_add_in_unversioned,
 
657
        once dirstate is stable and if it is merged with WorkingTree3, consider
 
658
        removing this copy of the test.
 
659
        """
 
660
        self.build_tree(['unversioned/', 'unversioned/a file'])
 
661
        state = dirstate.DirState.initialize('dirstate')
 
662
        try:
 
663
            self.assertRaises(errors.NotVersionedError, state.add,
 
664
                'unversioned/a file', 'a file id', 'file', None, None)
 
665
        finally:
 
666
            state.unlock()
 
667
 
 
668
    def test_add_directory_to_root_no_parents_all_data(self):
 
669
        # The most trivial addition of a dir is when there are no parents and
 
670
        # its in the root and all data about the file is supplied
 
671
        self.build_tree(['a dir/'])
 
672
        stat = os.lstat('a dir')
 
673
        expected_entries = [
 
674
            (('', '', 'TREE_ROOT'), [
 
675
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
676
             ]),
 
677
            (('', 'a dir', 'a dir id'), [
 
678
             ('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
 
679
             ]),
 
680
            ]
 
681
        state = dirstate.DirState.initialize('dirstate')
 
682
        try:
 
683
            state.add('a dir', 'a dir id', 'directory', stat, None)
 
684
            # having added it, it should be in the output of iter_entries.
 
685
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
686
            # saving and reloading should not affect this.
 
687
            state.save()
 
688
        finally:
 
689
            state.unlock()
 
690
        state = dirstate.DirState.on_file('dirstate')
 
691
        state.lock_read()
 
692
        state._validate()
 
693
        try:
 
694
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
695
        finally:
 
696
            state.unlock()
 
697
 
 
698
    def test_add_symlink_to_root_no_parents_all_data(self):
 
699
        # The most trivial addition of a symlink when there are no parents and
 
700
        # its in the root and all data about the file is supplied
 
701
        ## TODO: windows: dont fail this test. Also, how are symlinks meant to
 
702
        # be represented on windows.
 
703
        os.symlink('target', 'a link')
 
704
        stat = os.lstat('a link')
 
705
        expected_entries = [
 
706
            (('', '', 'TREE_ROOT'), [
 
707
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
708
             ]),
 
709
            (('', 'a link', 'a link id'), [
 
710
             ('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
 
711
             ]),
 
712
            ]
 
713
        state = dirstate.DirState.initialize('dirstate')
 
714
        try:
 
715
            state.add('a link', 'a link id', 'symlink', stat, 'target')
 
716
            # having added it, it should be in the output of iter_entries.
 
717
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
718
            # saving and reloading should not affect this.
 
719
            state.save()
 
720
        finally:
 
721
            state.unlock()
 
722
        state = dirstate.DirState.on_file('dirstate')
 
723
        state.lock_read()
 
724
        try:
 
725
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
726
        finally:
 
727
            state.unlock()
 
728
 
 
729
    def test_add_directory_and_child_no_parents_all_data(self):
 
730
        # after adding a directory, we should be able to add children to it.
 
731
        self.build_tree(['a dir/', 'a dir/a file'])
 
732
        dirstat = os.lstat('a dir')
 
733
        filestat = os.lstat('a dir/a file')
 
734
        expected_entries = [
 
735
            (('', '', 'TREE_ROOT'), [
 
736
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
 
737
             ]),
 
738
            (('', 'a dir', 'a dir id'), [
 
739
             ('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
 
740
             ]),
 
741
            (('a dir', 'a file', 'a file id'), [
 
742
             ('f', '1'*20, 25, False,
 
743
              dirstate.pack_stat(filestat)), # current tree details
 
744
             ]),
 
745
            ]
 
746
        state = dirstate.DirState.initialize('dirstate')
 
747
        try:
 
748
            state.add('a dir', 'a dir id', 'directory', dirstat, None)
 
749
            state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
 
750
            # added it, it should be in the output of iter_entries.
 
751
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
752
            # saving and reloading should not affect this.
 
753
            state.save()
 
754
        finally:
 
755
            state.unlock()
 
756
        state = dirstate.DirState.on_file('dirstate')
 
757
        state.lock_read()
 
758
        try:
 
759
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
760
        finally:
 
761
            state.unlock()
 
762
 
 
763
    def test_add_tree_reference(self):
 
764
        # make a dirstate and add a tree reference
 
765
        state = dirstate.DirState.initialize('dirstate')
 
766
        expected_entry = (
 
767
            ('', 'subdir', 'subdir-id'),
 
768
            [('t', 'subtree-123123', 0, False,
 
769
              'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
 
770
            )
 
771
        try:
 
772
            state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
 
773
            entry = state._get_entry(0, 'subdir-id', 'subdir')
 
774
            self.assertEqual(entry, expected_entry)
 
775
            state._validate()
 
776
            state.save()
 
777
        finally:
 
778
            state.unlock()
 
779
        # now check we can read it back
 
780
        state.lock_read()
 
781
        state._validate()
 
782
        try:
 
783
            entry2 = state._get_entry(0, 'subdir-id', 'subdir')
 
784
            self.assertEqual(entry, entry2)
 
785
            self.assertEqual(entry, expected_entry)
 
786
            # and lookup by id should work too
 
787
            entry2 = state._get_entry(0, fileid_utf8='subdir-id')
 
788
            self.assertEqual(entry, expected_entry)
 
789
        finally:
 
790
            state.unlock()
 
791
 
 
792
    def test_add_forbidden_names(self):
 
793
        state = dirstate.DirState.initialize('dirstate')
 
794
        self.addCleanup(state.unlock)
 
795
        self.assertRaises(errors.BzrError,
 
796
            state.add, '.', 'ass-id', 'directory', None, None)
 
797
        self.assertRaises(errors.BzrError,
 
798
            state.add, '..', 'ass-id', 'directory', None, None)
 
799
 
 
800
 
 
801
class TestGetLines(TestCaseWithDirState):
 
802
 
 
803
    def test_get_line_with_2_rows(self):
 
804
        state = self.create_dirstate_with_root_and_subdir()
 
805
        try:
 
806
            self.assertEqual(['#bazaar dirstate flat format 3\n',
 
807
                'adler32: -1327947603\n',
 
808
                'num_entries: 2\n',
 
809
                '0\x00\n\x00'
 
810
                '0\x00\n\x00'
 
811
                '\x00\x00a-root-value\x00'
 
812
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
 
813
                '\x00subdir\x00subdir-id\x00'
 
814
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
 
815
                ], state.get_lines())
 
816
        finally:
 
817
            state.unlock()
 
818
 
 
819
    def test_entry_to_line(self):
 
820
        state = self.create_dirstate_with_root()
 
821
        try:
 
822
            self.assertEqual(
 
823
                '\x00\x00a-root-value\x00d\x00\x000\x00n'
 
824
                '\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
 
825
                state._entry_to_line(state._dirblocks[0][1][0]))
 
826
        finally:
 
827
            state.unlock()
 
828
 
 
829
    def test_entry_to_line_with_parent(self):
 
830
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
831
        root_entry = ('', '', 'a-root-value'), [
 
832
            ('d', '', 0, False, packed_stat), # current tree details
 
833
             # first: a pointer to the current location
 
834
            ('a', 'dirname/basename', 0, False, ''),
 
835
            ]
 
836
        state = dirstate.DirState.initialize('dirstate')
 
837
        try:
 
838
            self.assertEqual(
 
839
                '\x00\x00a-root-value\x00'
 
840
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
 
841
                'a\x00dirname/basename\x000\x00n\x00',
 
842
                state._entry_to_line(root_entry))
 
843
        finally:
 
844
            state.unlock()
 
845
 
 
846
    def test_entry_to_line_with_two_parents_at_different_paths(self):
 
847
        # / in the tree, at / in one parent and /dirname/basename in the other.
 
848
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
849
        root_entry = ('', '', 'a-root-value'), [
 
850
            ('d', '', 0, False, packed_stat), # current tree details
 
851
            ('d', '', 0, False, 'rev_id'), # first parent details
 
852
             # second: a pointer to the current location
 
853
            ('a', 'dirname/basename', 0, False, ''),
 
854
            ]
 
855
        state = dirstate.DirState.initialize('dirstate')
 
856
        try:
 
857
            self.assertEqual(
 
858
                '\x00\x00a-root-value\x00'
 
859
                'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
 
860
                'd\x00\x000\x00n\x00rev_id\x00'
 
861
                'a\x00dirname/basename\x000\x00n\x00',
 
862
                state._entry_to_line(root_entry))
 
863
        finally:
 
864
            state.unlock()
 
865
 
 
866
    def test_iter_entries(self):
 
867
        # we should be able to iterate the dirstate entries from end to end
 
868
        # this is for get_lines to be easy to read.
 
869
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
 
870
        dirblocks = []
 
871
        root_entries = [(('', '', 'a-root-value'), [
 
872
            ('d', '', 0, False, packed_stat), # current tree details
 
873
            ])]
 
874
        dirblocks.append(('', root_entries))
 
875
        # add two files in the root
 
876
        subdir_entry = ('', 'subdir', 'subdir-id'), [
 
877
            ('d', '', 0, False, packed_stat), # current tree details
 
878
            ]
 
879
        afile_entry = ('', 'afile', 'afile-id'), [
 
880
            ('f', 'sha1value', 34, False, packed_stat), # current tree details
 
881
            ]
 
882
        dirblocks.append(('', [subdir_entry, afile_entry]))
 
883
        # and one in subdir
 
884
        file_entry2 = ('subdir', '2file', '2file-id'), [
 
885
            ('f', 'sha1value', 23, False, packed_stat), # current tree details
 
886
            ]
 
887
        dirblocks.append(('subdir', [file_entry2]))
 
888
        state = dirstate.DirState.initialize('dirstate')
 
889
        try:
 
890
            state._set_data([], dirblocks)
 
891
            expected_entries = [root_entries[0], subdir_entry, afile_entry,
 
892
                                file_entry2]
 
893
            self.assertEqual(expected_entries, list(state._iter_entries()))
 
894
        finally:
 
895
            state.unlock()
 
896
 
 
897
 
 
898
class TestGetBlockRowIndex(TestCaseWithDirState):
 
899
 
 
900
    def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
 
901
        file_present, state, dirname, basename, tree_index):
 
902
        self.assertEqual((block_index, row_index, dir_present, file_present),
 
903
            state._get_block_entry_index(dirname, basename, tree_index))
 
904
        if dir_present:
 
905
            block = state._dirblocks[block_index]
 
906
            self.assertEqual(dirname, block[0])
 
907
        if dir_present and file_present:
 
908
            row = state._dirblocks[block_index][1][row_index]
 
909
            self.assertEqual(dirname, row[0][0])
 
910
            self.assertEqual(basename, row[0][1])
 
911
 
 
912
    def test_simple_structure(self):
 
913
        state = self.create_dirstate_with_root_and_subdir()
 
914
        self.addCleanup(state.unlock)
 
915
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
 
916
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
 
917
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
 
918
        self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
 
919
        self.assertBlockRowIndexEqual(2, 0, False, False, state,
 
920
                                      'subdir', 'foo', 0)
 
921
 
 
922
    def test_complex_structure_exists(self):
 
923
        state = self.create_complex_dirstate()
 
924
        self.addCleanup(state.unlock)
 
925
        # Make sure we can find everything that exists
 
926
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
 
927
        self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
 
928
        self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
 
929
        self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
 
930
        self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
 
931
        self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
 
932
        self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
 
933
        self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
 
934
        self.assertBlockRowIndexEqual(3, 1, True, True, state,
 
935
                                      'b', 'h\xc3\xa5', 0)
 
936
 
 
937
    def test_complex_structure_missing(self):
 
938
        state = self.create_complex_dirstate()
 
939
        self.addCleanup(state.unlock)
 
940
        # Make sure things would be inserted in the right locations
 
941
        # '_' comes before 'a'
 
942
        self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
 
943
        self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
 
944
        self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
 
945
        self.assertBlockRowIndexEqual(1, 4, True, False, state,
 
946
                                      '', 'h\xc3\xa5', 0)
 
947
        self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
 
948
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
 
949
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
 
950
        # This would be inserted between a/ and b/
 
951
        self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
 
952
        # Put at the end
 
953
        self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
 
954
 
 
955
 
 
956
class TestGetEntry(TestCaseWithDirState):
 
957
 
 
958
    def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
 
959
        """Check that the right entry is returned for a request to getEntry."""
 
960
        entry = state._get_entry(index, path_utf8=path)
 
961
        if file_id is None:
 
962
            self.assertEqual((None, None), entry)
 
963
        else:
 
964
            cur = entry[0]
 
965
            self.assertEqual((dirname, basename, file_id), cur[:3])
 
966
 
 
967
    def test_simple_structure(self):
 
968
        state = self.create_dirstate_with_root_and_subdir()
 
969
        self.addCleanup(state.unlock)
 
970
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
971
        self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
 
972
        self.assertEntryEqual(None, None, None, state, 'missing', 0)
 
973
        self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
 
974
        self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
 
975
 
 
976
    def test_complex_structure_exists(self):
 
977
        state = self.create_complex_dirstate()
 
978
        self.addCleanup(state.unlock)
 
979
        self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
980
        self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
 
981
        self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
 
982
        self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
 
983
        self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
 
984
        self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
 
985
        self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
 
986
        self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
 
987
        self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
 
988
                              'b/h\xc3\xa5', 0)
 
989
 
 
990
    def test_complex_structure_missing(self):
 
991
        state = self.create_complex_dirstate()
 
992
        self.addCleanup(state.unlock)
 
993
        self.assertEntryEqual(None, None, None, state, '_', 0)
 
994
        self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
 
995
        self.assertEntryEqual(None, None, None, state, 'a/b', 0)
 
996
        self.assertEntryEqual(None, None, None, state, 'c/d', 0)
 
997
 
 
998
    def test_get_entry_uninitialized(self):
 
999
        """Calling get_entry will load data if it needs to"""
 
1000
        state = self.create_dirstate_with_root()
 
1001
        try:
 
1002
            state.save()
 
1003
        finally:
 
1004
            state.unlock()
 
1005
        del state
 
1006
        state = dirstate.DirState.on_file('dirstate')
 
1007
        state.lock_read()
 
1008
        try:
 
1009
            self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
1010
                             state._header_state)
 
1011
            self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
1012
                             state._dirblock_state)
 
1013
            self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
 
1014
        finally:
 
1015
            state.unlock()
 
1016
 
 
1017
 
 
1018
class TestDirstateSortOrder(TestCaseWithTransport):
 
1019
    """Test that DirState adds entries in the right order."""
 
1020
 
 
1021
    def test_add_sorting(self):
 
1022
        """Add entries in lexicographical order, we get path sorted order.
 
1023
 
 
1024
        This tests it to a depth of 4, to make sure we don't just get it right
 
1025
        at a single depth. 'a/a' should come before 'a-a', even though it
 
1026
        doesn't lexicographically.
 
1027
        """
 
1028
        dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
 
1029
                'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
 
1030
               ]
 
1031
        null_sha = ''
 
1032
        state = dirstate.DirState.initialize('dirstate')
 
1033
        self.addCleanup(state.unlock)
 
1034
 
 
1035
        fake_stat = os.stat('dirstate')
 
1036
        for d in dirs:
 
1037
            d_id = d.replace('/', '_')+'-id'
 
1038
            file_path = d + '/f'
 
1039
            file_id = file_path.replace('/', '_')+'-id'
 
1040
            state.add(d, d_id, 'directory', fake_stat, null_sha)
 
1041
            state.add(file_path, file_id, 'file', fake_stat, null_sha)
 
1042
 
 
1043
        expected = ['', '', 'a',
 
1044
                'a/a', 'a/a/a', 'a/a/a/a',
 
1045
                'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
 
1046
               ]
 
1047
        split = lambda p:p.split('/')
 
1048
        self.assertEqual(sorted(expected, key=split), expected)
 
1049
        dirblock_names = [d[0] for d in state._dirblocks]
 
1050
        self.assertEqual(expected, dirblock_names)
 
1051
 
 
1052
    def test_set_parent_trees_correct_order(self):
 
1053
        """After calling set_parent_trees() we should maintain the order."""
 
1054
        dirs = ['a', 'a-a', 'a/a']
 
1055
        null_sha = ''
 
1056
        state = dirstate.DirState.initialize('dirstate')
 
1057
        self.addCleanup(state.unlock)
 
1058
 
 
1059
        fake_stat = os.stat('dirstate')
 
1060
        for d in dirs:
 
1061
            d_id = d.replace('/', '_')+'-id'
 
1062
            file_path = d + '/f'
 
1063
            file_id = file_path.replace('/', '_')+'-id'
 
1064
            state.add(d, d_id, 'directory', fake_stat, null_sha)
 
1065
            state.add(file_path, file_id, 'file', fake_stat, null_sha)
 
1066
 
 
1067
        expected = ['', '', 'a', 'a/a', 'a-a']
 
1068
        dirblock_names = [d[0] for d in state._dirblocks]
 
1069
        self.assertEqual(expected, dirblock_names)
 
1070
 
 
1071
        # *really* cheesy way to just get an empty tree
 
1072
        repo = self.make_repository('repo')
 
1073
        empty_tree = repo.revision_tree(None)
 
1074
        state.set_parent_trees([('null:', empty_tree)], [])
 
1075
 
 
1076
        dirblock_names = [d[0] for d in state._dirblocks]
 
1077
        self.assertEqual(expected, dirblock_names)
 
1078
 
 
1079
 
 
1080
class InstrumentedDirState(dirstate.DirState):
 
1081
    """An DirState with instrumented sha1 functionality."""
 
1082
 
 
1083
    def __init__(self, path):
 
1084
        super(InstrumentedDirState, self).__init__(path)
 
1085
        self._time_offset = 0
 
1086
        self._log = []
 
1087
 
 
1088
    def _sha_cutoff_time(self):
 
1089
        timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
 
1090
        self._cutoff_time = timestamp + self._time_offset
 
1091
 
 
1092
    def _sha1_file(self, abspath, entry):
 
1093
        self._log.append(('sha1', abspath))
 
1094
        return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
 
1095
 
 
1096
    def _read_link(self, abspath, old_link):
 
1097
        self._log.append(('read_link', abspath, old_link))
 
1098
        return super(InstrumentedDirState, self)._read_link(abspath, old_link)
 
1099
 
 
1100
    def _lstat(self, abspath, entry):
 
1101
        self._log.append(('lstat', abspath))
 
1102
        return super(InstrumentedDirState, self)._lstat(abspath, entry)
 
1103
 
 
1104
    def _is_executable(self, mode, old_executable):
 
1105
        self._log.append(('is_exec', mode, old_executable))
 
1106
        return super(InstrumentedDirState, self)._is_executable(mode,
 
1107
                                                                old_executable)
 
1108
 
 
1109
    def adjust_time(self, secs):
 
1110
        """Move the clock forward or back.
 
1111
 
 
1112
        :param secs: The amount to adjust the clock by. Positive values make it
 
1113
        seem as if we are in the future, negative values make it seem like we
 
1114
        are in the past.
 
1115
        """
 
1116
        self._time_offset += secs
 
1117
        self._cutoff_time = None
 
1118
 
 
1119
 
 
1120
class _FakeStat(object):
 
1121
    """A class with the same attributes as a real stat result."""
 
1122
 
 
1123
    def __init__(self, size, mtime, ctime, dev, ino, mode):
 
1124
        self.st_size = size
 
1125
        self.st_mtime = mtime
 
1126
        self.st_ctime = ctime
 
1127
        self.st_dev = dev
 
1128
        self.st_ino = ino
 
1129
        self.st_mode = mode
 
1130
 
 
1131
 
 
1132
class TestUpdateEntry(TestCaseWithDirState):
 
1133
    """Test the DirState.update_entry functions"""
 
1134
 
 
1135
    def get_state_with_a(self):
 
1136
        """Create a DirState tracking a single object named 'a'"""
 
1137
        state = InstrumentedDirState.initialize('dirstate')
 
1138
        self.addCleanup(state.unlock)
 
1139
        state.add('a', 'a-id', 'file', None, '')
 
1140
        entry = state._get_entry(0, path_utf8='a')
 
1141
        return state, entry
 
1142
 
 
1143
    def test_update_entry(self):
 
1144
        state, entry = self.get_state_with_a()
 
1145
        self.build_tree(['a'])
 
1146
        # Add one where we don't provide the stat or sha already
 
1147
        self.assertEqual(('', 'a', 'a-id'), entry[0])
 
1148
        self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
 
1149
                         entry[1])
 
1150
        # Flush the buffers to disk
 
1151
        state.save()
 
1152
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1153
                         state._dirblock_state)
 
1154
 
 
1155
        stat_value = os.lstat('a')
 
1156
        packed_stat = dirstate.pack_stat(stat_value)
 
1157
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1158
                                          stat_value=stat_value)
 
1159
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1160
                         link_or_sha1)
 
1161
 
 
1162
        # The dirblock entry should be updated with the new info
 
1163
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
 
1164
                         entry[1])
 
1165
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
1166
                         state._dirblock_state)
 
1167
        mode = stat_value.st_mode
 
1168
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
 
1169
 
 
1170
        state.save()
 
1171
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1172
                         state._dirblock_state)
 
1173
 
 
1174
        # If we do it again right away, we don't know if the file has changed
 
1175
        # so we will re-read the file. Roll the clock back so the file is
 
1176
        # guaranteed to look too new.
 
1177
        state.adjust_time(-10)
 
1178
 
 
1179
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1180
                                          stat_value=stat_value)
 
1181
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
 
1182
                          ('sha1', 'a'), ('is_exec', mode, False),
 
1183
                         ], state._log)
 
1184
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1185
                         link_or_sha1)
 
1186
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
1187
                         state._dirblock_state)
 
1188
        state.save()
 
1189
 
 
1190
        # However, if we move the clock forward so the file is considered
 
1191
        # "stable", it should just returned the cached value.
 
1192
        state.adjust_time(20)
 
1193
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1194
                                          stat_value=stat_value)
 
1195
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1196
                         link_or_sha1)
 
1197
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
 
1198
                          ('sha1', 'a'), ('is_exec', mode, False),
 
1199
                         ], state._log)
 
1200
 
 
1201
    def test_update_entry_no_stat_value(self):
 
1202
        """Passing the stat_value is optional."""
 
1203
        state, entry = self.get_state_with_a()
 
1204
        state.adjust_time(-10) # Make sure the file looks new
 
1205
        self.build_tree(['a'])
 
1206
        # Add one where we don't provide the stat or sha already
 
1207
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1208
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1209
                         link_or_sha1)
 
1210
        stat_value = os.lstat('a')
 
1211
        self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
 
1212
                          ('is_exec', stat_value.st_mode, False),
 
1213
                         ], state._log)
 
1214
 
 
1215
    def test_update_entry_symlink(self):
 
1216
        """Update entry should read symlinks."""
 
1217
        if not osutils.has_symlinks():
 
1218
            return # PlatformDeficiency / TestSkipped
 
1219
        state, entry = self.get_state_with_a()
 
1220
        state.save()
 
1221
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1222
                         state._dirblock_state)
 
1223
        os.symlink('target', 'a')
 
1224
 
 
1225
        state.adjust_time(-10) # Make the symlink look new
 
1226
        stat_value = os.lstat('a')
 
1227
        packed_stat = dirstate.pack_stat(stat_value)
 
1228
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1229
                                          stat_value=stat_value)
 
1230
        self.assertEqual('target', link_or_sha1)
 
1231
        self.assertEqual([('read_link', 'a', '')], state._log)
 
1232
        # Dirblock is updated
 
1233
        self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
 
1234
                         entry[1])
 
1235
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
1236
                         state._dirblock_state)
 
1237
 
 
1238
        # Because the stat_value looks new, we should re-read the target
 
1239
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1240
                                          stat_value=stat_value)
 
1241
        self.assertEqual('target', link_or_sha1)
 
1242
        self.assertEqual([('read_link', 'a', ''),
 
1243
                          ('read_link', 'a', 'target'),
 
1244
                         ], state._log)
 
1245
        state.adjust_time(+20) # Skip into the future, all files look old
 
1246
        link_or_sha1 = state.update_entry(entry, abspath='a',
 
1247
                                          stat_value=stat_value)
 
1248
        self.assertEqual('target', link_or_sha1)
 
1249
        # There should not be a new read_link call.
 
1250
        # (this is a weak assertion, because read_link is fairly inexpensive,
 
1251
        # versus the number of symlinks that we would have)
 
1252
        self.assertEqual([('read_link', 'a', ''),
 
1253
                          ('read_link', 'a', 'target'),
 
1254
                         ], state._log)
 
1255
 
 
1256
    def test_update_entry_dir(self):
 
1257
        state, entry = self.get_state_with_a()
 
1258
        self.build_tree(['a/'])
 
1259
        self.assertIs(None, state.update_entry(entry, 'a'))
 
1260
 
 
1261
    def create_and_test_file(self, state, entry):
 
1262
        """Create a file at 'a' and verify the state finds it.
 
1263
 
 
1264
        The state should already be versioning *something* at 'a'. This makes
 
1265
        sure that state.update_entry recognizes it as a file.
 
1266
        """
 
1267
        self.build_tree(['a'])
 
1268
        stat_value = os.lstat('a')
 
1269
        packed_stat = dirstate.pack_stat(stat_value)
 
1270
 
 
1271
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1272
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
1273
                         link_or_sha1)
 
1274
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
 
1275
                         entry[1])
 
1276
        return packed_stat
 
1277
 
 
1278
    def create_and_test_dir(self, state, entry):
 
1279
        """Create a directory at 'a' and verify the state finds it.
 
1280
 
 
1281
        The state should already be versioning *something* at 'a'. This makes
 
1282
        sure that state.update_entry recognizes it as a directory.
 
1283
        """
 
1284
        self.build_tree(['a/'])
 
1285
        stat_value = os.lstat('a')
 
1286
        packed_stat = dirstate.pack_stat(stat_value)
 
1287
 
 
1288
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1289
        self.assertIs(None, link_or_sha1)
 
1290
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
 
1291
 
 
1292
        return packed_stat
 
1293
 
 
1294
    def create_and_test_symlink(self, state, entry):
 
1295
        """Create a symlink at 'a' and verify the state finds it.
 
1296
 
 
1297
        The state should already be versioning *something* at 'a'. This makes
 
1298
        sure that state.update_entry recognizes it as a symlink.
 
1299
 
 
1300
        This should not be called if this platform does not have symlink
 
1301
        support.
 
1302
        """
 
1303
        os.symlink('path/to/foo', 'a')
 
1304
 
 
1305
        stat_value = os.lstat('a')
 
1306
        packed_stat = dirstate.pack_stat(stat_value)
 
1307
 
 
1308
        link_or_sha1 = state.update_entry(entry, abspath='a')
 
1309
        self.assertEqual('path/to/foo', link_or_sha1)
 
1310
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
 
1311
                         entry[1])
 
1312
        return packed_stat
 
1313
 
 
1314
    def test_update_missing_file(self):
 
1315
        state, entry = self.get_state_with_a()
 
1316
        packed_stat = self.create_and_test_file(state, entry)
 
1317
        # Now if we delete the file, update_entry should recover and
 
1318
        # return None.
 
1319
        os.remove('a')
 
1320
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1321
        # And the record shouldn't be changed.
 
1322
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
 
1323
        self.assertEqual([('f', digest, 14, False, packed_stat)],
 
1324
                         entry[1])
 
1325
 
 
1326
    def test_update_missing_dir(self):
 
1327
        state, entry = self.get_state_with_a()
 
1328
        packed_stat = self.create_and_test_dir(state, entry)
 
1329
        # Now if we delete the directory, update_entry should recover and
 
1330
        # return None.
 
1331
        os.rmdir('a')
 
1332
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1333
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
 
1334
 
 
1335
    def test_update_missing_symlink(self):
 
1336
        if not osutils.has_symlinks():
 
1337
            return # PlatformDeficiency / TestSkipped
 
1338
        state, entry = self.get_state_with_a()
 
1339
        packed_stat = self.create_and_test_symlink(state, entry)
 
1340
        os.remove('a')
 
1341
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1342
        # And the record shouldn't be changed.
 
1343
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
 
1344
                         entry[1])
 
1345
 
 
1346
    def test_update_file_to_dir(self):
 
1347
        """If a file changes to a directory we return None for the sha.
 
1348
        We also update the inventory record.
 
1349
        """
 
1350
        state, entry = self.get_state_with_a()
 
1351
        self.create_and_test_file(state, entry)
 
1352
        os.remove('a')
 
1353
        self.create_and_test_dir(state, entry)
 
1354
 
 
1355
    def test_update_file_to_symlink(self):
 
1356
        """File becomes a symlink"""
 
1357
        if not osutils.has_symlinks():
 
1358
            return # PlatformDeficiency / TestSkipped
 
1359
        state, entry = self.get_state_with_a()
 
1360
        self.create_and_test_file(state, entry)
 
1361
        os.remove('a')
 
1362
        self.create_and_test_symlink(state, entry)
 
1363
 
 
1364
    def test_update_dir_to_file(self):
 
1365
        """Directory becoming a file updates the entry."""
 
1366
        state, entry = self.get_state_with_a()
 
1367
        self.create_and_test_dir(state, entry)
 
1368
        os.rmdir('a')
 
1369
        self.create_and_test_file(state, entry)
 
1370
 
 
1371
    def test_update_dir_to_symlink(self):
 
1372
        """Directory becomes a symlink"""
 
1373
        if not osutils.has_symlinks():
 
1374
            return # PlatformDeficiency / TestSkipped
 
1375
        state, entry = self.get_state_with_a()
 
1376
        self.create_and_test_dir(state, entry)
 
1377
        os.rmdir('a')
 
1378
        self.create_and_test_symlink(state, entry)
 
1379
 
 
1380
    def test_update_symlink_to_file(self):
 
1381
        """Symlink becomes a file"""
 
1382
        state, entry = self.get_state_with_a()
 
1383
        self.create_and_test_symlink(state, entry)
 
1384
        os.remove('a')
 
1385
        self.create_and_test_file(state, entry)
 
1386
 
 
1387
    def test_update_symlink_to_dir(self):
 
1388
        """Symlink becomes a directory"""
 
1389
        state, entry = self.get_state_with_a()
 
1390
        self.create_and_test_symlink(state, entry)
 
1391
        os.remove('a')
 
1392
        self.create_and_test_dir(state, entry)
 
1393
 
 
1394
    def test__is_executable_win32(self):
 
1395
        state, entry = self.get_state_with_a()
 
1396
        self.build_tree(['a'])
 
1397
 
 
1398
        # Make sure we are using the win32 implementation of _is_executable
 
1399
        state._is_executable = state._is_executable_win32
 
1400
 
 
1401
        # The file on disk is not executable, but we are marking it as though
 
1402
        # it is. With _is_executable_win32 we ignore what is on disk.
 
1403
        entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
 
1404
 
 
1405
        stat_value = os.lstat('a')
 
1406
        packed_stat = dirstate.pack_stat(stat_value)
 
1407
 
 
1408
        state.adjust_time(-10) # Make sure everything is new
 
1409
        # Make sure it wants to kkkkkkkk
 
1410
        state.update_entry(entry, abspath='a', stat_value=stat_value)
 
1411
 
 
1412
        # The row is updated, but the executable bit stays set.
 
1413
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
 
1414
        self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
 
1415
 
 
1416
 
 
1417
class TestPackStat(TestCaseWithTransport):
 
1418
 
 
1419
    def assertPackStat(self, expected, stat_value):
 
1420
        """Check the packed and serialized form of a stat value."""
 
1421
        self.assertEqual(expected, dirstate.pack_stat(stat_value))
 
1422
 
 
1423
    def test_pack_stat_int(self):
 
1424
        st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
 
1425
        # Make sure that all parameters have an impact on the packed stat.
 
1426
        self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1427
        st.st_size = 7000L
 
1428
        #                ay0 => bWE
 
1429
        self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1430
        st.st_mtime = 1172758620
 
1431
        #                     4FZ => 4Fx
 
1432
        self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
 
1433
        st.st_ctime = 1172758630
 
1434
        #                          uBZ => uBm
 
1435
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1436
        st.st_dev = 888L
 
1437
        #                                DCQ => DeA
 
1438
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
 
1439
        st.st_ino = 6499540L
 
1440
        #                                     LNI => LNQ
 
1441
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
 
1442
        st.st_mode = 0100744
 
1443
        #                                          IGk => IHk
 
1444
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
 
1445
 
 
1446
    def test_pack_stat_float(self):
 
1447
        """On some platforms mtime and ctime are floats.
 
1448
 
 
1449
        Make sure we don't get warnings or errors, and that we ignore changes <
 
1450
        1s
 
1451
        """
 
1452
        st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
 
1453
                       777L, 6499538L, 0100644)
 
1454
        # These should all be the same as the integer counterparts
 
1455
        self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
 
1456
        st.st_mtime = 1172758620.0
 
1457
        #                     FZF5 => FxF5
 
1458
        self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
 
1459
        st.st_ctime = 1172758630.0
 
1460
        #                          uBZ => uBm
 
1461
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1462
        # fractional seconds are discarded, so no change from above
 
1463
        st.st_mtime = 1172758620.453
 
1464
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1465
        st.st_ctime = 1172758630.228
 
1466
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
 
1467
 
 
1468
 
 
1469
class TestBisect(TestCaseWithTransport):
 
1470
    """Test the ability to bisect into the disk format."""
 
1471
 
 
1472
    def create_basic_dirstate(self):
 
1473
        """Create a dirstate with a few files and directories.
 
1474
 
 
1475
            a
 
1476
            b/
 
1477
              c
 
1478
              d/
 
1479
                e
 
1480
            f
 
1481
        """
 
1482
        tree = self.make_branch_and_tree('tree')
 
1483
        paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
 
1484
        file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
 
1485
        self.build_tree(['tree/' + p for p in paths])
 
1486
        tree.set_root_id('TREE_ROOT')
 
1487
        tree.add([p.rstrip('/') for p in paths], file_ids)
 
1488
        tree.commit('initial', rev_id='rev-1')
 
1489
        revision_id = 'rev-1'
 
1490
        # a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
 
1491
        t = self.get_transport().clone('tree')
 
1492
        a_text = t.get_bytes('a')
 
1493
        a_sha = osutils.sha_string(a_text)
 
1494
        a_len = len(a_text)
 
1495
        # b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
 
1496
        # c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
 
1497
        c_text = t.get_bytes('b/c')
 
1498
        c_sha = osutils.sha_string(c_text)
 
1499
        c_len = len(c_text)
 
1500
        # d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
 
1501
        # e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
 
1502
        e_text = t.get_bytes('b/d/e')
 
1503
        e_sha = osutils.sha_string(e_text)
 
1504
        e_len = len(e_text)
 
1505
        # f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
 
1506
        f_text = t.get_bytes('f')
 
1507
        f_sha = osutils.sha_string(f_text)
 
1508
        f_len = len(f_text)
 
1509
        null_stat = dirstate.DirState.NULLSTAT
 
1510
        expected = {
 
1511
            '':(('', '', 'TREE_ROOT'), [
 
1512
                  ('d', '', 0, False, null_stat),
 
1513
                  ('d', '', 0, False, revision_id),
 
1514
                ]),
 
1515
            'a':(('', 'a', 'a-id'), [
 
1516
                   ('f', '', 0, False, null_stat),
 
1517
                   ('f', a_sha, a_len, False, revision_id),
 
1518
                 ]),
 
1519
            'b':(('', 'b', 'b-id'), [
 
1520
                  ('d', '', 0, False, null_stat),
 
1521
                  ('d', '', 0, False, revision_id),
 
1522
                 ]),
 
1523
            'b/c':(('b', 'c', 'c-id'), [
 
1524
                    ('f', '', 0, False, null_stat),
 
1525
                    ('f', c_sha, c_len, False, revision_id),
 
1526
                   ]),
 
1527
            'b/d':(('b', 'd', 'd-id'), [
 
1528
                    ('d', '', 0, False, null_stat),
 
1529
                    ('d', '', 0, False, revision_id),
 
1530
                   ]),
 
1531
            'b/d/e':(('b/d', 'e', 'e-id'), [
 
1532
                      ('f', '', 0, False, null_stat),
 
1533
                      ('f', e_sha, e_len, False, revision_id),
 
1534
                     ]),
 
1535
            'f':(('', 'f', 'f-id'), [
 
1536
                  ('f', '', 0, False, null_stat),
 
1537
                  ('f', f_sha, f_len, False, revision_id),
 
1538
                 ]),
 
1539
        }
 
1540
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
1541
        try:
 
1542
            state.save()
 
1543
        finally:
 
1544
            state.unlock()
 
1545
        # Use a different object, to make sure nothing is pre-cached in memory.
 
1546
        state = dirstate.DirState.on_file('dirstate')
 
1547
        state.lock_read()
 
1548
        self.addCleanup(state.unlock)
 
1549
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
1550
                         state._dirblock_state)
 
1551
        # This is code is only really tested if we actually have to make more
 
1552
        # than one read, so set the page size to something smaller.
 
1553
        # We want it to contain about 2.2 records, so that we have a couple
 
1554
        # records that we can read per attempt
 
1555
        state._bisect_page_size = 200
 
1556
        return tree, state, expected
 
1557
 
 
1558
    def create_duplicated_dirstate(self):
 
1559
        """Create a dirstate with a deleted and added entries.
 
1560
 
 
1561
        This grabs a basic_dirstate, and then removes and re adds every entry
 
1562
        with a new file id.
 
1563
        """
 
1564
        tree, state, expected = self.create_basic_dirstate()
 
1565
        # Now we will just remove and add every file so we get an extra entry
 
1566
        # per entry. Unversion in reverse order so we handle subdirs
 
1567
        tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
 
1568
        tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
 
1569
                 ['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
 
1570
 
 
1571
        # Update the expected dictionary.
 
1572
        for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
 
1573
            orig = expected[path]
 
1574
            path2 = path + '2'
 
1575
            # This record was deleted in the current tree
 
1576
            expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
 
1577
                                        orig[1][1]])
 
1578
            new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
 
1579
            # And didn't exist in the basis tree
 
1580
            expected[path2] = (new_key, [orig[1][0],
 
1581
                                         dirstate.DirState.NULL_PARENT_DETAILS])
 
1582
 
 
1583
        # We will replace the 'dirstate' file underneath 'state', but that is
 
1584
        # okay as lock as we unlock 'state' first.
 
1585
        state.unlock()
 
1586
        try:
 
1587
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
 
1588
            try:
 
1589
                new_state.save()
 
1590
            finally:
 
1591
                new_state.unlock()
 
1592
        finally:
 
1593
            # But we need to leave state in a read-lock because we already have
 
1594
            # a cleanup scheduled
 
1595
            state.lock_read()
 
1596
        return tree, state, expected
 
1597
 
 
1598
    def create_renamed_dirstate(self):
 
1599
        """Create a dirstate with a few internal renames.
 
1600
 
 
1601
        This takes the basic dirstate, and moves the paths around.
 
1602
        """
 
1603
        tree, state, expected = self.create_basic_dirstate()
 
1604
        # Rename a file
 
1605
        tree.rename_one('a', 'b/g')
 
1606
        # And a directory
 
1607
        tree.rename_one('b/d', 'h')
 
1608
 
 
1609
        old_a = expected['a']
 
1610
        expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
 
1611
        expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
 
1612
                                                ('r', 'a', 0, False, '')])
 
1613
        old_d = expected['b/d']
 
1614
        expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
 
1615
        expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
 
1616
                                             ('r', 'b/d', 0, False, '')])
 
1617
 
 
1618
        old_e = expected['b/d/e']
 
1619
        expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
 
1620
                             old_e[1][1]])
 
1621
        expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
 
1622
                                                ('r', 'b/d/e', 0, False, '')])
 
1623
 
 
1624
        state.unlock()
 
1625
        try:
 
1626
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
 
1627
            try:
 
1628
                new_state.save()
 
1629
            finally:
 
1630
                new_state.unlock()
 
1631
        finally:
 
1632
            state.lock_read()
 
1633
        return tree, state, expected
 
1634
 
 
1635
    def assertBisect(self, expected_map, map_keys, state, paths):
 
1636
        """Assert that bisecting for paths returns the right result.
 
1637
 
 
1638
        :param expected_map: A map from key => entry value
 
1639
        :param map_keys: The keys to expect for each path
 
1640
        :param state: The DirState object.
 
1641
        :param paths: A list of paths, these will automatically be split into
 
1642
                      (dir, name) tuples, and sorted according to how _bisect
 
1643
                      requires.
 
1644
        """
 
1645
        dir_names = sorted(osutils.split(p) for p in paths)
 
1646
        result = state._bisect(dir_names)
 
1647
        # For now, results are just returned in whatever order we read them.
 
1648
        # We could sort by (dir, name, file_id) or something like that, but in
 
1649
        # the end it would still be fairly arbitrary, and we don't want the
 
1650
        # extra overhead if we can avoid it. So sort everything to make sure
 
1651
        # equality is true
 
1652
        assert len(map_keys) == len(dir_names)
 
1653
        expected = {}
 
1654
        for dir_name, keys in zip(dir_names, map_keys):
 
1655
            if keys is None:
 
1656
                # This should not be present in the output
 
1657
                continue
 
1658
            expected[dir_name] = sorted(expected_map[k] for k in keys)
 
1659
 
 
1660
        for dir_name in result:
 
1661
            result[dir_name].sort()
 
1662
 
 
1663
        self.assertEqual(expected, result)
 
1664
 
 
1665
    def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
 
1666
        """Assert that bisecting for dirbblocks returns the right result.
 
1667
 
 
1668
        :param expected_map: A map from key => expected values
 
1669
        :param map_keys: A nested list of paths we expect to be returned.
 
1670
            Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
 
1671
        :param state: The DirState object.
 
1672
        :param paths: A list of directories
 
1673
        """
 
1674
        result = state._bisect_dirblocks(paths)
 
1675
        assert len(map_keys) == len(paths)
 
1676
 
 
1677
        expected = {}
 
1678
        for path, keys in zip(paths, map_keys):
 
1679
            if keys is None:
 
1680
                # This should not be present in the output
 
1681
                continue
 
1682
            expected[path] = sorted(expected_map[k] for k in keys)
 
1683
        for path in result:
 
1684
            result[path].sort()
 
1685
 
 
1686
        self.assertEqual(expected, result)
 
1687
 
 
1688
    def assertBisectRecursive(self, expected_map, map_keys, state, paths):
 
1689
        """Assert the return value of a recursive bisection.
 
1690
 
 
1691
        :param expected_map: A map from key => entry value
 
1692
        :param map_keys: A list of paths we expect to be returned.
 
1693
            Something like ['a', 'b', 'f', 'b/d', 'b/d2']
 
1694
        :param state: The DirState object.
 
1695
        :param paths: A list of files and directories. It will be broken up
 
1696
            into (dir, name) pairs and sorted before calling _bisect_recursive.
 
1697
        """
 
1698
        expected = {}
 
1699
        for key in map_keys:
 
1700
            entry = expected_map[key]
 
1701
            dir_name_id, trees_info = entry
 
1702
            expected[dir_name_id] = trees_info
 
1703
 
 
1704
        dir_names = sorted(osutils.split(p) for p in paths)
 
1705
        result = state._bisect_recursive(dir_names)
 
1706
 
 
1707
        self.assertEqual(expected, result)
 
1708
 
 
1709
    def test_bisect_each(self):
 
1710
        """Find a single record using bisect."""
 
1711
        tree, state, expected = self.create_basic_dirstate()
 
1712
 
 
1713
        # Bisect should return the rows for the specified files.
 
1714
        self.assertBisect(expected, [['']], state, [''])
 
1715
        self.assertBisect(expected, [['a']], state, ['a'])
 
1716
        self.assertBisect(expected, [['b']], state, ['b'])
 
1717
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
 
1718
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1719
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1720
        self.assertBisect(expected, [['f']], state, ['f'])
 
1721
 
 
1722
    def test_bisect_multi(self):
 
1723
        """Bisect can be used to find multiple records at the same time."""
 
1724
        tree, state, expected = self.create_basic_dirstate()
 
1725
        # Bisect should be capable of finding multiple entries at the same time
 
1726
        self.assertBisect(expected, [['a'], ['b'], ['f']],
 
1727
                          state, ['a', 'b', 'f'])
 
1728
        # ('', 'f') sorts before the others
 
1729
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
 
1730
                          state, ['b/d', 'b/d/e', 'f'])
 
1731
 
 
1732
    def test_bisect_one_page(self):
 
1733
        """Test bisect when there is only 1 page to read"""
 
1734
        tree, state, expected = self.create_basic_dirstate()
 
1735
        state._bisect_page_size = 5000
 
1736
        self.assertBisect(expected,[['']], state, [''])
 
1737
        self.assertBisect(expected,[['a']], state, ['a'])
 
1738
        self.assertBisect(expected,[['b']], state, ['b'])
 
1739
        self.assertBisect(expected,[['b/c']], state, ['b/c'])
 
1740
        self.assertBisect(expected,[['b/d']], state, ['b/d'])
 
1741
        self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
 
1742
        self.assertBisect(expected,[['f']], state, ['f'])
 
1743
        self.assertBisect(expected,[['a'], ['b'], ['f']],
 
1744
                          state, ['a', 'b', 'f'])
 
1745
        # ('', 'f') sorts before the others
 
1746
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
 
1747
                          state, ['b/d', 'b/d/e', 'f'])
 
1748
 
 
1749
    def test_bisect_duplicate_paths(self):
 
1750
        """When bisecting for a path, handle multiple entries."""
 
1751
        tree, state, expected = self.create_duplicated_dirstate()
 
1752
 
 
1753
        # Now make sure that both records are properly returned.
 
1754
        self.assertBisect(expected, [['']], state, [''])
 
1755
        self.assertBisect(expected, [['a', 'a2']], state, ['a'])
 
1756
        self.assertBisect(expected, [['b', 'b2']], state, ['b'])
 
1757
        self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
 
1758
        self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
 
1759
        self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
 
1760
                          state, ['b/d/e'])
 
1761
        self.assertBisect(expected, [['f', 'f2']], state, ['f'])
 
1762
 
 
1763
    def test_bisect_page_size_too_small(self):
 
1764
        """If the page size is too small, we will auto increase it."""
 
1765
        tree, state, expected = self.create_basic_dirstate()
 
1766
        state._bisect_page_size = 50
 
1767
        self.assertBisect(expected, [None], state, ['b/e'])
 
1768
        self.assertBisect(expected, [['a']], state, ['a'])
 
1769
        self.assertBisect(expected, [['b']], state, ['b'])
 
1770
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
 
1771
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1772
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1773
        self.assertBisect(expected, [['f']], state, ['f'])
 
1774
 
 
1775
    def test_bisect_missing(self):
 
1776
        """Test that bisect return None if it cannot find a path."""
 
1777
        tree, state, expected = self.create_basic_dirstate()
 
1778
        self.assertBisect(expected, [None], state, ['foo'])
 
1779
        self.assertBisect(expected, [None], state, ['b/foo'])
 
1780
        self.assertBisect(expected, [None], state, ['bar/foo'])
 
1781
 
 
1782
        self.assertBisect(expected, [['a'], None, ['b/d']],
 
1783
                          state, ['a', 'foo', 'b/d'])
 
1784
 
 
1785
    def test_bisect_rename(self):
 
1786
        """Check that we find a renamed row."""
 
1787
        tree, state, expected = self.create_renamed_dirstate()
 
1788
 
 
1789
        # Search for the pre and post renamed entries
 
1790
        self.assertBisect(expected, [['a']], state, ['a'])
 
1791
        self.assertBisect(expected, [['b/g']], state, ['b/g'])
 
1792
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
 
1793
        self.assertBisect(expected, [['h']], state, ['h'])
 
1794
 
 
1795
        # What about b/d/e? shouldn't that also get 2 directory entries?
 
1796
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
 
1797
        self.assertBisect(expected, [['h/e']], state, ['h/e'])
 
1798
 
 
1799
    def test_bisect_dirblocks(self):
 
1800
        tree, state, expected = self.create_duplicated_dirstate()
 
1801
        self.assertBisectDirBlocks(expected,
 
1802
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
 
1803
        self.assertBisectDirBlocks(expected,
 
1804
            [['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
 
1805
        self.assertBisectDirBlocks(expected,
 
1806
            [['b/d/e', 'b/d/e2']], state, ['b/d'])
 
1807
        self.assertBisectDirBlocks(expected,
 
1808
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
 
1809
             ['b/c', 'b/c2', 'b/d', 'b/d2'],
 
1810
             ['b/d/e', 'b/d/e2'],
 
1811
            ], state, ['', 'b', 'b/d'])
 
1812
 
 
1813
    def test_bisect_dirblocks_missing(self):
 
1814
        tree, state, expected = self.create_basic_dirstate()
 
1815
        self.assertBisectDirBlocks(expected, [['b/d/e'], None],
 
1816
            state, ['b/d', 'b/e'])
 
1817
        # Files don't show up in this search
 
1818
        self.assertBisectDirBlocks(expected, [None], state, ['a'])
 
1819
        self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
 
1820
        self.assertBisectDirBlocks(expected, [None], state, ['c'])
 
1821
        self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
 
1822
        self.assertBisectDirBlocks(expected, [None], state, ['f'])
 
1823
 
 
1824
    def test_bisect_recursive_each(self):
 
1825
        tree, state, expected = self.create_basic_dirstate()
 
1826
        self.assertBisectRecursive(expected, ['a'], state, ['a'])
 
1827
        self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
 
1828
        self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
 
1829
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
 
1830
                                   state, ['b/d'])
 
1831
        self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
 
1832
                                   state, ['b'])
 
1833
        self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
 
1834
                                              'b/d', 'b/d/e'],
 
1835
                                   state, [''])
 
1836
 
 
1837
    def test_bisect_recursive_multiple(self):
 
1838
        tree, state, expected = self.create_basic_dirstate()
 
1839
        self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
 
1840
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
 
1841
                                   state, ['b/d', 'b/d/e'])
 
1842
 
 
1843
    def test_bisect_recursive_missing(self):
 
1844
        tree, state, expected = self.create_basic_dirstate()
 
1845
        self.assertBisectRecursive(expected, [], state, ['d'])
 
1846
        self.assertBisectRecursive(expected, [], state, ['b/e'])
 
1847
        self.assertBisectRecursive(expected, [], state, ['g'])
 
1848
        self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
 
1849
 
 
1850
    def test_bisect_recursive_renamed(self):
 
1851
        tree, state, expected = self.create_renamed_dirstate()
 
1852
 
 
1853
        # Looking for either renamed item should find the other
 
1854
        self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
 
1855
        self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
 
1856
        # Looking in the containing directory should find the rename target,
 
1857
        # and anything in a subdir of the renamed target.
 
1858
        self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
 
1859
                                              'b/d/e', 'b/g', 'h', 'h/e'],
 
1860
                                   state, ['b'])
 
1861
 
 
1862
 
 
1863
class TestBisectDirblock(TestCase):
 
1864
    """Test that bisect_dirblock() returns the expected values.
 
1865
 
 
1866
    bisect_dirblock is intended to work like bisect.bisect_left() except it
 
1867
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
 
1868
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
 
1869
    """
 
1870
 
 
1871
    def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
 
1872
        """Assert that bisect_split works like bisect_left on the split paths.
 
1873
 
 
1874
        :param dirblocks: A list of (path, [info]) pairs.
 
1875
        :param split_dirblocks: A list of ((split, path), [info]) pairs.
 
1876
        :param path: The path we are indexing.
 
1877
 
 
1878
        All other arguments will be passed along.
 
1879
        """
 
1880
        bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
 
1881
                                                 *args, **kwargs)
 
1882
        split_dirblock = (path.split('/'), [])
 
1883
        bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
 
1884
                                             *args)
 
1885
        self.assertEqual(bisect_left_idx, bisect_split_idx,
 
1886
                         'bisect_split disagreed. %s != %s'
 
1887
                         ' for key %s'
 
1888
                         % (bisect_left_idx, bisect_split_idx, path)
 
1889
                         )
 
1890
 
 
1891
    def paths_to_dirblocks(self, paths):
 
1892
        """Convert a list of paths into dirblock form.
 
1893
 
 
1894
        Also, ensure that the paths are in proper sorted order.
 
1895
        """
 
1896
        dirblocks = [(path, []) for path in paths]
 
1897
        split_dirblocks = [(path.split('/'), []) for path in paths]
 
1898
        self.assertEqual(sorted(split_dirblocks), split_dirblocks)
 
1899
        return dirblocks, split_dirblocks
 
1900
 
 
1901
    def test_simple(self):
 
1902
        """In the simple case it works just like bisect_left"""
 
1903
        paths = ['', 'a', 'b', 'c', 'd']
 
1904
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
1905
        for path in paths:
 
1906
            self.assertBisect(dirblocks, split_dirblocks, path)
 
1907
        self.assertBisect(dirblocks, split_dirblocks, '_')
 
1908
        self.assertBisect(dirblocks, split_dirblocks, 'aa')
 
1909
        self.assertBisect(dirblocks, split_dirblocks, 'bb')
 
1910
        self.assertBisect(dirblocks, split_dirblocks, 'cc')
 
1911
        self.assertBisect(dirblocks, split_dirblocks, 'dd')
 
1912
        self.assertBisect(dirblocks, split_dirblocks, 'a/a')
 
1913
        self.assertBisect(dirblocks, split_dirblocks, 'b/b')
 
1914
        self.assertBisect(dirblocks, split_dirblocks, 'c/c')
 
1915
        self.assertBisect(dirblocks, split_dirblocks, 'd/d')
 
1916
 
 
1917
    def test_involved(self):
 
1918
        """This is where bisect_left diverges slightly."""
 
1919
        paths = ['', 'a',
 
1920
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
1921
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
1922
                 'a-a', 'a-z',
 
1923
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
1924
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
1925
                 'z-a', 'z-z',
 
1926
                ]
 
1927
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
1928
        for path in paths:
 
1929
            self.assertBisect(dirblocks, split_dirblocks, path)
 
1930
 
 
1931
    def test_involved_cached(self):
 
1932
        """This is where bisect_left diverges slightly."""
 
1933
        paths = ['', 'a',
 
1934
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
1935
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
1936
                 'a-a', 'a-z',
 
1937
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
1938
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
1939
                 'z-a', 'z-z',
 
1940
                ]
 
1941
        cache = {}
 
1942
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
1943
        for path in paths:
 
1944
            self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
 
1945