1
# Copyright (C) 2006, 2007 Canonical Ltd
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.
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.
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
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
28
from bzrlib.memorytree import MemoryTree
29
from bzrlib.osutils import has_symlinks
30
from bzrlib.tests import (
32
TestCaseWithTransport,
39
# general checks for NOT_IN_MEMORY error conditions.
40
# set_path_id on a NOT_IN_MEMORY dirstate
41
# set_path_id unicode support
42
# set_path_id setting id of a path not root
43
# set_path_id setting id when there are parents without the id in the parents
44
# set_path_id setting id when there are parents with the id in the parents
45
# set_path_id setting id when state is not in memory
46
# set_path_id setting id when state is in memory unmodified
47
# set_path_id setting id when state is in memory modified
50
class TestCaseWithDirState(TestCaseWithTransport):
51
"""Helper functions for creating DirState objects with various content."""
53
def create_empty_dirstate(self):
54
"""Return a locked but empty dirstate"""
55
state = dirstate.DirState.initialize('dirstate')
58
def create_dirstate_with_root(self):
59
"""Return a write-locked state with a single root entry."""
60
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
61
root_entry_direntry = ('', '', 'a-root-value'), [
62
('d', '', 0, False, packed_stat),
65
dirblocks.append(('', [root_entry_direntry]))
66
dirblocks.append(('', []))
67
state = self.create_empty_dirstate()
69
state._set_data([], dirblocks)
76
def create_dirstate_with_root_and_subdir(self):
77
"""Return a locked DirState with a root and a subdir"""
78
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
79
subdir_entry = ('', 'subdir', 'subdir-id'), [
80
('d', '', 0, False, packed_stat),
82
state = self.create_dirstate_with_root()
84
dirblocks = list(state._dirblocks)
85
dirblocks[1][1].append(subdir_entry)
86
state._set_data([], dirblocks)
92
def create_complex_dirstate(self):
93
"""This dirstate contains multiple files and directories.
103
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
105
# Notice that a/e is an empty directory.
107
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
108
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
109
root_entry = ('', '', 'a-root-value'), [
110
('d', '', 0, False, packed_stat),
112
a_entry = ('', 'a', 'a-dir'), [
113
('d', '', 0, False, packed_stat),
115
b_entry = ('', 'b', 'b-dir'), [
116
('d', '', 0, False, packed_stat),
118
c_entry = ('', 'c', 'c-file'), [
119
('f', null_sha, 10, False, packed_stat),
121
d_entry = ('', 'd', 'd-file'), [
122
('f', null_sha, 20, False, packed_stat),
124
e_entry = ('a', 'e', 'e-dir'), [
125
('d', '', 0, False, packed_stat),
127
f_entry = ('a', 'f', 'f-file'), [
128
('f', null_sha, 30, False, packed_stat),
130
g_entry = ('b', 'g', 'g-file'), [
131
('f', null_sha, 30, False, packed_stat),
133
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
134
('f', null_sha, 40, False, packed_stat),
137
dirblocks.append(('', [root_entry]))
138
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
139
dirblocks.append(('a', [e_entry, f_entry]))
140
dirblocks.append(('b', [g_entry, h_entry]))
141
state = dirstate.DirState.initialize('dirstate')
144
state._set_data([], dirblocks)
150
def check_state_with_reopen(self, expected_result, state):
151
"""Check that state has current state expected_result.
153
This will check the current state, open the file anew and check it
155
This function expects the current state to be locked for writing, and
156
will unlock it before re-opening.
157
This is required because we can't open a lock_read() while something
158
else has a lock_write().
159
write => mutually exclusive lock
162
# The state should already be write locked, since we just had to do
163
# some operation to get here.
164
assert state._lock_token is not None
166
self.assertEqual(expected_result[0], state.get_parent_ids())
167
# there should be no ghosts in this tree.
168
self.assertEqual([], state.get_ghosts())
169
# there should be one fileid in this tree - the root of the tree.
170
self.assertEqual(expected_result[1], list(state._iter_entries()))
174
del state # Callers should unlock
175
state = dirstate.DirState.on_file('dirstate')
178
self.assertEqual(expected_result[1], list(state._iter_entries()))
183
class TestTreeToDirState(TestCaseWithDirState):
185
def test_empty_to_dirstate(self):
186
"""We should be able to create a dirstate for an empty tree."""
187
# There are no files on disk and no parents
188
tree = self.make_branch_and_tree('tree')
189
expected_result = ([], [
190
(('', '', tree.path2id('')), # common details
191
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
193
state = dirstate.DirState.from_tree(tree, 'dirstate')
195
self.check_state_with_reopen(expected_result, state)
197
def test_1_parents_empty_to_dirstate(self):
198
# create a parent by doing a commit
199
tree = self.make_branch_and_tree('tree')
200
rev_id = tree.commit('first post').encode('utf8')
201
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
202
expected_result = ([rev_id], [
203
(('', '', tree.path2id('')), # common details
204
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
205
('d', '', 0, False, rev_id), # first parent details
207
state = dirstate.DirState.from_tree(tree, 'dirstate')
208
self.check_state_with_reopen(expected_result, state)
211
def test_2_parents_empty_to_dirstate(self):
212
# create a parent by doing a commit
213
tree = self.make_branch_and_tree('tree')
214
rev_id = tree.commit('first post')
215
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
216
rev_id2 = tree2.commit('second post', allow_pointless=True)
217
tree.merge_from_branch(tree2.branch)
218
expected_result = ([rev_id, rev_id2], [
219
(('', '', tree.path2id('')), # common details
220
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
221
('d', '', 0, False, rev_id), # first parent details
222
('d', '', 0, False, rev_id2), # second parent details
224
state = dirstate.DirState.from_tree(tree, 'dirstate')
225
self.check_state_with_reopen(expected_result, state)
228
def test_empty_unknowns_are_ignored_to_dirstate(self):
229
"""We should be able to create a dirstate for an empty tree."""
230
# There are no files on disk and no parents
231
tree = self.make_branch_and_tree('tree')
232
self.build_tree(['tree/unknown'])
233
expected_result = ([], [
234
(('', '', tree.path2id('')), # common details
235
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
237
state = dirstate.DirState.from_tree(tree, 'dirstate')
238
self.check_state_with_reopen(expected_result, state)
240
def get_tree_with_a_file(self):
241
tree = self.make_branch_and_tree('tree')
242
self.build_tree(['tree/a file'])
243
tree.add('a file', 'a file id')
246
def test_non_empty_no_parents_to_dirstate(self):
247
"""We should be able to create a dirstate for an empty tree."""
248
# There are files on disk and no parents
249
tree = self.get_tree_with_a_file()
250
expected_result = ([], [
251
(('', '', tree.path2id('')), # common details
252
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
254
(('', 'a file', 'a file id'), # common
255
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
258
state = dirstate.DirState.from_tree(tree, 'dirstate')
259
self.check_state_with_reopen(expected_result, state)
261
def test_1_parents_not_empty_to_dirstate(self):
262
# create a parent by doing a commit
263
tree = self.get_tree_with_a_file()
264
rev_id = tree.commit('first post').encode('utf8')
265
# change the current content to be different this will alter stat, sha
267
self.build_tree_contents([('tree/a file', 'new content\n')])
268
expected_result = ([rev_id], [
269
(('', '', tree.path2id('')), # common details
270
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
271
('d', '', 0, False, rev_id), # first parent details
273
(('', 'a file', 'a file id'), # common
274
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
275
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
276
rev_id), # first parent
279
state = dirstate.DirState.from_tree(tree, 'dirstate')
280
self.check_state_with_reopen(expected_result, state)
282
def test_2_parents_not_empty_to_dirstate(self):
283
# create a parent by doing a commit
284
tree = self.get_tree_with_a_file()
285
rev_id = tree.commit('first post').encode('utf8')
286
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
287
# change the current content to be different this will alter stat, sha
289
self.build_tree_contents([('tree2/a file', 'merge content\n')])
290
rev_id2 = tree2.commit('second post').encode('utf8')
291
tree.merge_from_branch(tree2.branch)
292
# change the current content to be different this will alter stat, sha
293
# and length again, giving us three distinct values:
294
self.build_tree_contents([('tree/a file', 'new content\n')])
295
expected_result = ([rev_id, rev_id2], [
296
(('', '', tree.path2id('')), # common details
297
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
298
('d', '', 0, False, rev_id), # first parent details
299
('d', '', 0, False, rev_id2), # second parent details
301
(('', 'a file', 'a file id'), # common
302
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
303
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
304
rev_id), # first parent
305
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
306
rev_id2), # second parent
309
state = dirstate.DirState.from_tree(tree, 'dirstate')
310
self.check_state_with_reopen(expected_result, state)
312
def test_colliding_fileids(self):
313
# test insertion of parents creating several entries at the same path.
314
# we used to have a bug where they could cause the dirstate to break
315
# its ordering invariants.
316
# create some trees to test from
319
tree = self.make_branch_and_tree('tree%d' % i)
320
self.build_tree(['tree%d/name' % i,])
321
tree.add(['name'], ['file-id%d' % i])
322
revision_id = 'revid-%d' % i
323
tree.commit('message', rev_id=revision_id)
324
parents.append((revision_id,
325
tree.branch.repository.revision_tree(revision_id)))
326
# now fold these trees into a dirstate
327
state = dirstate.DirState.initialize('dirstate')
329
state.set_parent_trees(parents, [])
335
class TestDirStateOnFile(TestCaseWithDirState):
337
def test_construct_with_path(self):
338
tree = self.make_branch_and_tree('tree')
339
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
340
# we want to be able to get the lines of the dirstate that we will
342
lines = state.get_lines()
344
self.build_tree_contents([('dirstate', ''.join(lines))])
346
# no parents, default tree content
347
expected_result = ([], [
348
(('', '', tree.path2id('')), # common details
349
# current tree details, but new from_tree skips statting, it
350
# uses set_state_from_inventory, and thus depends on the
352
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
355
state = dirstate.DirState.on_file('dirstate')
356
state.lock_write() # check_state_with_reopen will save() and unlock it
357
self.check_state_with_reopen(expected_result, state)
359
def test_can_save_clean_on_file(self):
360
tree = self.make_branch_and_tree('tree')
361
state = dirstate.DirState.from_tree(tree, 'dirstate')
363
# doing a save should work here as there have been no changes.
365
# TODO: stat it and check it hasn't changed; may require waiting
366
# for the state accuracy window.
371
class TestDirStateInitialize(TestCaseWithDirState):
373
def test_initialize(self):
374
expected_result = ([], [
375
(('', '', 'TREE_ROOT'), # common details
376
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
379
state = dirstate.DirState.initialize('dirstate')
381
self.assertIsInstance(state, dirstate.DirState)
382
lines = state.get_lines()
383
self.assertFileEqual(''.join(state.get_lines()),
385
self.check_state_with_reopen(expected_result, state)
391
class TestDirStateManipulations(TestCaseWithDirState):
393
def test_set_state_from_inventory_no_content_no_parents(self):
394
# setting the current inventory is a slow but important api to support.
395
tree1 = self.make_branch_and_memory_tree('tree1')
399
revid1 = tree1.commit('foo').encode('utf8')
400
root_id = tree1.inventory.root.file_id
401
inv = tree1.inventory
404
expected_result = [], [
405
(('', '', root_id), [
406
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
407
state = dirstate.DirState.initialize('dirstate')
409
state.set_state_from_inventory(inv)
410
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
412
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
413
state._dirblock_state)
418
# This will unlock it
419
self.check_state_with_reopen(expected_result, state)
421
def test_set_path_id_no_parents(self):
422
"""The id of a path can be changed trivally with no parents."""
423
state = dirstate.DirState.initialize('dirstate')
425
# check precondition to be sure the state does change appropriately.
427
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
428
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
429
list(state._iter_entries()))
430
state.set_path_id('', 'foobarbaz')
432
(('', '', 'foobarbaz'), [('d', '', 0, False,
433
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
434
self.assertEqual(expected_rows, list(state._iter_entries()))
435
# should work across save too
439
state = dirstate.DirState.on_file('dirstate')
443
self.assertEqual(expected_rows, list(state._iter_entries()))
447
def test_set_path_id_with_parents(self):
448
"""Set the root file id in a dirstate with parents"""
449
mt = self.make_branch_and_tree('mt')
450
# in case the default tree format uses a different root id
451
mt.set_root_id('TREE_ROOT')
452
mt.commit('foo', rev_id='parent-revid')
453
rt = mt.branch.repository.revision_tree('parent-revid')
454
state = dirstate.DirState.initialize('dirstate')
457
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
458
state.set_path_id('', 'foobarbaz')
460
# now see that it is what we expected
462
(('', '', 'TREE_ROOT'),
463
[('a', '', 0, False, ''),
464
('d', '', 0, False, 'parent-revid'),
466
(('', '', 'foobarbaz'),
467
[('d', '', 0, False, ''),
468
('a', '', 0, False, ''),
472
self.assertEqual(expected_rows, list(state._iter_entries()))
473
# should work across save too
477
# now flush & check we get the same
478
state = dirstate.DirState.on_file('dirstate')
482
self.assertEqual(expected_rows, list(state._iter_entries()))
485
# now change within an existing file-backed state
489
state.set_path_id('', 'tree-root-2')
495
def test_set_parent_trees_no_content(self):
496
# set_parent_trees is a slow but important api to support.
497
tree1 = self.make_branch_and_memory_tree('tree1')
501
revid1 = tree1.commit('foo')
504
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
505
tree2 = MemoryTree.create_on_branch(branch2)
508
revid2 = tree2.commit('foo')
509
root_id = tree2.inventory.root.file_id
512
state = dirstate.DirState.initialize('dirstate')
514
state.set_path_id('', root_id)
515
state.set_parent_trees(
516
((revid1, tree1.branch.repository.revision_tree(revid1)),
517
(revid2, tree2.branch.repository.revision_tree(revid2)),
518
('ghost-rev', None)),
520
# check we can reopen and use the dirstate after setting parent
527
state = dirstate.DirState.on_file('dirstate')
530
self.assertEqual([revid1, revid2, 'ghost-rev'],
531
state.get_parent_ids())
532
# iterating the entire state ensures that the state is parsable.
533
list(state._iter_entries())
534
# be sure that it sets not appends - change it
535
state.set_parent_trees(
536
((revid1, tree1.branch.repository.revision_tree(revid1)),
537
('ghost-rev', None)),
539
# and now put it back.
540
state.set_parent_trees(
541
((revid1, tree1.branch.repository.revision_tree(revid1)),
542
(revid2, tree2.branch.repository.revision_tree(revid2)),
543
('ghost-rev', tree2.branch.repository.revision_tree(None))),
545
self.assertEqual([revid1, revid2, 'ghost-rev'],
546
state.get_parent_ids())
547
# the ghost should be recorded as such by set_parent_trees.
548
self.assertEqual(['ghost-rev'], state.get_ghosts())
550
[(('', '', root_id), [
551
('d', '', 0, False, dirstate.DirState.NULLSTAT),
552
('d', '', 0, False, revid1),
553
('d', '', 0, False, revid2)
555
list(state._iter_entries()))
559
def test_set_parent_trees_file_missing_from_tree(self):
560
# Adding a parent tree may reference files not in the current state.
561
# they should get listed just once by id, even if they are in two
563
# set_parent_trees is a slow but important api to support.
564
tree1 = self.make_branch_and_memory_tree('tree1')
568
tree1.add(['a file'], ['file-id'], ['file'])
569
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
570
revid1 = tree1.commit('foo')
573
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
574
tree2 = MemoryTree.create_on_branch(branch2)
577
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
578
revid2 = tree2.commit('foo')
579
root_id = tree2.inventory.root.file_id
582
# check the layout in memory
583
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
584
(('', '', root_id), [
585
('d', '', 0, False, dirstate.DirState.NULLSTAT),
586
('d', '', 0, False, revid1.encode('utf8')),
587
('d', '', 0, False, revid2.encode('utf8'))
589
(('', 'a file', 'file-id'), [
590
('a', '', 0, False, ''),
591
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
592
revid1.encode('utf8')),
593
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
594
revid2.encode('utf8'))
597
state = dirstate.DirState.initialize('dirstate')
599
state.set_path_id('', root_id)
600
state.set_parent_trees(
601
((revid1, tree1.branch.repository.revision_tree(revid1)),
602
(revid2, tree2.branch.repository.revision_tree(revid2)),
608
# check_state_with_reopen will unlock
609
self.check_state_with_reopen(expected_result, state)
611
### add a path via _set_data - so we dont need delta work, just
612
# raw data in, and ensure that it comes out via get_lines happily.
614
def test_add_path_to_root_no_parents_all_data(self):
615
# The most trivial addition of a path is when there are no parents and
616
# its in the root and all data about the file is supplied
617
self.build_tree(['a file'])
618
stat = os.lstat('a file')
619
# the 1*20 is the sha1 pretend value.
620
state = dirstate.DirState.initialize('dirstate')
622
(('', '', 'TREE_ROOT'), [
623
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
625
(('', 'a file', 'a file id'), [
626
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
630
state.add('a file', 'a file id', 'file', stat, '1'*20)
631
# having added it, it should be in the output of iter_entries.
632
self.assertEqual(expected_entries, list(state._iter_entries()))
633
# saving and reloading should not affect this.
637
state = dirstate.DirState.on_file('dirstate')
640
self.assertEqual(expected_entries, list(state._iter_entries()))
644
def test_add_path_to_unversioned_directory(self):
645
"""Adding a path to an unversioned directory should error.
647
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
648
once dirstate is stable and if it is merged with WorkingTree3, consider
649
removing this copy of the test.
651
self.build_tree(['unversioned/', 'unversioned/a file'])
652
state = dirstate.DirState.initialize('dirstate')
654
self.assertRaises(errors.NotVersionedError, state.add,
655
'unversioned/a file', 'a file id', 'file', None, None)
659
def test_add_directory_to_root_no_parents_all_data(self):
660
# The most trivial addition of a dir is when there are no parents and
661
# its in the root and all data about the file is supplied
662
self.build_tree(['a dir/'])
663
stat = os.lstat('a dir')
665
(('', '', 'TREE_ROOT'), [
666
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
668
(('', 'a dir', 'a dir id'), [
669
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
672
state = dirstate.DirState.initialize('dirstate')
674
state.add('a dir', 'a dir id', 'directory', stat, None)
675
# having added it, it should be in the output of iter_entries.
676
self.assertEqual(expected_entries, list(state._iter_entries()))
677
# saving and reloading should not affect this.
681
state = dirstate.DirState.on_file('dirstate')
685
self.assertEqual(expected_entries, list(state._iter_entries()))
689
def test_add_symlink_to_root_no_parents_all_data(self):
690
# The most trivial addition of a symlink when there are no parents and
691
# its in the root and all data about the file is supplied
692
# bzr doesn't support fake symlinks on windows, yet.
693
if not has_symlinks():
694
raise TestSkipped("No symlink support")
695
os.symlink('target', 'a link')
696
stat = os.lstat('a link')
698
(('', '', 'TREE_ROOT'), [
699
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
701
(('', 'a link', 'a link id'), [
702
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
705
state = dirstate.DirState.initialize('dirstate')
707
state.add('a link', 'a link id', 'symlink', stat, 'target')
708
# having added it, it should be in the output of iter_entries.
709
self.assertEqual(expected_entries, list(state._iter_entries()))
710
# saving and reloading should not affect this.
714
state = dirstate.DirState.on_file('dirstate')
717
self.assertEqual(expected_entries, list(state._iter_entries()))
721
def test_add_directory_and_child_no_parents_all_data(self):
722
# after adding a directory, we should be able to add children to it.
723
self.build_tree(['a dir/', 'a dir/a file'])
724
dirstat = os.lstat('a dir')
725
filestat = os.lstat('a dir/a file')
727
(('', '', 'TREE_ROOT'), [
728
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
730
(('', 'a dir', 'a dir id'), [
731
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
733
(('a dir', 'a file', 'a file id'), [
734
('f', '1'*20, 25, False,
735
dirstate.pack_stat(filestat)), # current tree details
738
state = dirstate.DirState.initialize('dirstate')
740
state.add('a dir', 'a dir id', 'directory', dirstat, None)
741
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
742
# added it, it should be in the output of iter_entries.
743
self.assertEqual(expected_entries, list(state._iter_entries()))
744
# saving and reloading should not affect this.
748
state = dirstate.DirState.on_file('dirstate')
751
self.assertEqual(expected_entries, list(state._iter_entries()))
755
def test_add_tree_reference(self):
756
# make a dirstate and add a tree reference
757
state = dirstate.DirState.initialize('dirstate')
759
('', 'subdir', 'subdir-id'),
760
[('t', 'subtree-123123', 0, False,
761
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
764
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
765
entry = state._get_entry(0, 'subdir-id', 'subdir')
766
self.assertEqual(entry, expected_entry)
771
# now check we can read it back
775
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
776
self.assertEqual(entry, entry2)
777
self.assertEqual(entry, expected_entry)
778
# and lookup by id should work too
779
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
780
self.assertEqual(entry, expected_entry)
784
def test_add_forbidden_names(self):
785
state = dirstate.DirState.initialize('dirstate')
786
self.addCleanup(state.unlock)
787
self.assertRaises(errors.BzrError,
788
state.add, '.', 'ass-id', 'directory', None, None)
789
self.assertRaises(errors.BzrError,
790
state.add, '..', 'ass-id', 'directory', None, None)
793
class TestGetLines(TestCaseWithDirState):
795
def test_get_line_with_2_rows(self):
796
state = self.create_dirstate_with_root_and_subdir()
798
self.assertEqual(['#bazaar dirstate flat format 3\n',
803
'\x00\x00a-root-value\x00'
804
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
805
'\x00subdir\x00subdir-id\x00'
806
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
807
], state.get_lines())
811
def test_entry_to_line(self):
812
state = self.create_dirstate_with_root()
815
'\x00\x00a-root-value\x00d\x00\x000\x00n'
816
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
817
state._entry_to_line(state._dirblocks[0][1][0]))
821
def test_entry_to_line_with_parent(self):
822
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
823
root_entry = ('', '', 'a-root-value'), [
824
('d', '', 0, False, packed_stat), # current tree details
825
# first: a pointer to the current location
826
('a', 'dirname/basename', 0, False, ''),
828
state = dirstate.DirState.initialize('dirstate')
831
'\x00\x00a-root-value\x00'
832
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
833
'a\x00dirname/basename\x000\x00n\x00',
834
state._entry_to_line(root_entry))
838
def test_entry_to_line_with_two_parents_at_different_paths(self):
839
# / in the tree, at / in one parent and /dirname/basename in the other.
840
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
841
root_entry = ('', '', 'a-root-value'), [
842
('d', '', 0, False, packed_stat), # current tree details
843
('d', '', 0, False, 'rev_id'), # first parent details
844
# second: a pointer to the current location
845
('a', 'dirname/basename', 0, False, ''),
847
state = dirstate.DirState.initialize('dirstate')
850
'\x00\x00a-root-value\x00'
851
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
852
'd\x00\x000\x00n\x00rev_id\x00'
853
'a\x00dirname/basename\x000\x00n\x00',
854
state._entry_to_line(root_entry))
858
def test_iter_entries(self):
859
# we should be able to iterate the dirstate entries from end to end
860
# this is for get_lines to be easy to read.
861
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
863
root_entries = [(('', '', 'a-root-value'), [
864
('d', '', 0, False, packed_stat), # current tree details
866
dirblocks.append(('', root_entries))
867
# add two files in the root
868
subdir_entry = ('', 'subdir', 'subdir-id'), [
869
('d', '', 0, False, packed_stat), # current tree details
871
afile_entry = ('', 'afile', 'afile-id'), [
872
('f', 'sha1value', 34, False, packed_stat), # current tree details
874
dirblocks.append(('', [subdir_entry, afile_entry]))
876
file_entry2 = ('subdir', '2file', '2file-id'), [
877
('f', 'sha1value', 23, False, packed_stat), # current tree details
879
dirblocks.append(('subdir', [file_entry2]))
880
state = dirstate.DirState.initialize('dirstate')
882
state._set_data([], dirblocks)
883
expected_entries = [root_entries[0], subdir_entry, afile_entry,
885
self.assertEqual(expected_entries, list(state._iter_entries()))
890
class TestGetBlockRowIndex(TestCaseWithDirState):
892
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
893
file_present, state, dirname, basename, tree_index):
894
self.assertEqual((block_index, row_index, dir_present, file_present),
895
state._get_block_entry_index(dirname, basename, tree_index))
897
block = state._dirblocks[block_index]
898
self.assertEqual(dirname, block[0])
899
if dir_present and file_present:
900
row = state._dirblocks[block_index][1][row_index]
901
self.assertEqual(dirname, row[0][0])
902
self.assertEqual(basename, row[0][1])
904
def test_simple_structure(self):
905
state = self.create_dirstate_with_root_and_subdir()
906
self.addCleanup(state.unlock)
907
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
908
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
909
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
910
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
911
self.assertBlockRowIndexEqual(2, 0, False, False, state,
914
def test_complex_structure_exists(self):
915
state = self.create_complex_dirstate()
916
self.addCleanup(state.unlock)
917
# Make sure we can find everything that exists
918
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
919
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
920
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
921
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
922
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
923
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
924
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
925
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
926
self.assertBlockRowIndexEqual(3, 1, True, True, state,
929
def test_complex_structure_missing(self):
930
state = self.create_complex_dirstate()
931
self.addCleanup(state.unlock)
932
# Make sure things would be inserted in the right locations
933
# '_' comes before 'a'
934
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
935
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
936
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
937
self.assertBlockRowIndexEqual(1, 4, True, False, state,
939
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
940
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
941
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
942
# This would be inserted between a/ and b/
943
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
945
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
948
class TestGetEntry(TestCaseWithDirState):
950
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
951
"""Check that the right entry is returned for a request to getEntry."""
952
entry = state._get_entry(index, path_utf8=path)
954
self.assertEqual((None, None), entry)
957
self.assertEqual((dirname, basename, file_id), cur[:3])
959
def test_simple_structure(self):
960
state = self.create_dirstate_with_root_and_subdir()
961
self.addCleanup(state.unlock)
962
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
963
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
964
self.assertEntryEqual(None, None, None, state, 'missing', 0)
965
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
966
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
968
def test_complex_structure_exists(self):
969
state = self.create_complex_dirstate()
970
self.addCleanup(state.unlock)
971
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
972
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
973
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
974
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
975
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
976
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
977
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
978
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
979
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
982
def test_complex_structure_missing(self):
983
state = self.create_complex_dirstate()
984
self.addCleanup(state.unlock)
985
self.assertEntryEqual(None, None, None, state, '_', 0)
986
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
987
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
988
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
990
def test_get_entry_uninitialized(self):
991
"""Calling get_entry will load data if it needs to"""
992
state = self.create_dirstate_with_root()
998
state = dirstate.DirState.on_file('dirstate')
1001
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1002
state._header_state)
1003
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1004
state._dirblock_state)
1005
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1010
class TestDirstateSortOrder(TestCaseWithTransport):
1011
"""Test that DirState adds entries in the right order."""
1013
def test_add_sorting(self):
1014
"""Add entries in lexicographical order, we get path sorted order.
1016
This tests it to a depth of 4, to make sure we don't just get it right
1017
at a single depth. 'a/a' should come before 'a-a', even though it
1018
doesn't lexicographically.
1020
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1021
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1024
state = dirstate.DirState.initialize('dirstate')
1025
self.addCleanup(state.unlock)
1027
fake_stat = os.stat('dirstate')
1029
d_id = d.replace('/', '_')+'-id'
1030
file_path = d + '/f'
1031
file_id = file_path.replace('/', '_')+'-id'
1032
state.add(d, d_id, 'directory', fake_stat, null_sha)
1033
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1035
expected = ['', '', 'a',
1036
'a/a', 'a/a/a', 'a/a/a/a',
1037
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1039
split = lambda p:p.split('/')
1040
self.assertEqual(sorted(expected, key=split), expected)
1041
dirblock_names = [d[0] for d in state._dirblocks]
1042
self.assertEqual(expected, dirblock_names)
1044
def test_set_parent_trees_correct_order(self):
1045
"""After calling set_parent_trees() we should maintain the order."""
1046
dirs = ['a', 'a-a', 'a/a']
1048
state = dirstate.DirState.initialize('dirstate')
1049
self.addCleanup(state.unlock)
1051
fake_stat = os.stat('dirstate')
1053
d_id = d.replace('/', '_')+'-id'
1054
file_path = d + '/f'
1055
file_id = file_path.replace('/', '_')+'-id'
1056
state.add(d, d_id, 'directory', fake_stat, null_sha)
1057
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1059
expected = ['', '', 'a', 'a/a', 'a-a']
1060
dirblock_names = [d[0] for d in state._dirblocks]
1061
self.assertEqual(expected, dirblock_names)
1063
# *really* cheesy way to just get an empty tree
1064
repo = self.make_repository('repo')
1065
empty_tree = repo.revision_tree(None)
1066
state.set_parent_trees([('null:', empty_tree)], [])
1068
dirblock_names = [d[0] for d in state._dirblocks]
1069
self.assertEqual(expected, dirblock_names)
1072
class InstrumentedDirState(dirstate.DirState):
1073
"""An DirState with instrumented sha1 functionality."""
1075
def __init__(self, path):
1076
super(InstrumentedDirState, self).__init__(path)
1077
self._time_offset = 0
1080
def _sha_cutoff_time(self):
1081
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1082
self._cutoff_time = timestamp + self._time_offset
1084
def _sha1_file(self, abspath, entry):
1085
self._log.append(('sha1', abspath))
1086
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1088
def _read_link(self, abspath, old_link):
1089
self._log.append(('read_link', abspath, old_link))
1090
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1092
def _lstat(self, abspath, entry):
1093
self._log.append(('lstat', abspath))
1094
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1096
def _is_executable(self, mode, old_executable):
1097
self._log.append(('is_exec', mode, old_executable))
1098
return super(InstrumentedDirState, self)._is_executable(mode,
1101
def adjust_time(self, secs):
1102
"""Move the clock forward or back.
1104
:param secs: The amount to adjust the clock by. Positive values make it
1105
seem as if we are in the future, negative values make it seem like we
1108
self._time_offset += secs
1109
self._cutoff_time = None
1112
class _FakeStat(object):
1113
"""A class with the same attributes as a real stat result."""
1115
def __init__(self, size, mtime, ctime, dev, ino, mode):
1117
self.st_mtime = mtime
1118
self.st_ctime = ctime
1124
class TestUpdateEntry(TestCaseWithDirState):
1125
"""Test the DirState.update_entry functions"""
1127
def get_state_with_a(self):
1128
"""Create a DirState tracking a single object named 'a'"""
1129
state = InstrumentedDirState.initialize('dirstate')
1130
self.addCleanup(state.unlock)
1131
state.add('a', 'a-id', 'file', None, '')
1132
entry = state._get_entry(0, path_utf8='a')
1135
def test_update_entry(self):
1136
state, entry = self.get_state_with_a()
1137
self.build_tree(['a'])
1138
# Add one where we don't provide the stat or sha already
1139
self.assertEqual(('', 'a', 'a-id'), entry[0])
1140
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1142
# Flush the buffers to disk
1144
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1145
state._dirblock_state)
1147
stat_value = os.lstat('a')
1148
packed_stat = dirstate.pack_stat(stat_value)
1149
link_or_sha1 = state.update_entry(entry, abspath='a',
1150
stat_value=stat_value)
1151
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1154
# The dirblock entry should be updated with the new info
1155
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1157
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1158
state._dirblock_state)
1159
mode = stat_value.st_mode
1160
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1163
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1164
state._dirblock_state)
1166
# If we do it again right away, we don't know if the file has changed
1167
# so we will re-read the file. Roll the clock back so the file is
1168
# guaranteed to look too new.
1169
state.adjust_time(-10)
1171
link_or_sha1 = state.update_entry(entry, abspath='a',
1172
stat_value=stat_value)
1173
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1174
('sha1', 'a'), ('is_exec', mode, False),
1176
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1178
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1179
state._dirblock_state)
1182
# However, if we move the clock forward so the file is considered
1183
# "stable", it should just returned the cached value.
1184
state.adjust_time(20)
1185
link_or_sha1 = state.update_entry(entry, abspath='a',
1186
stat_value=stat_value)
1187
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1189
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1190
('sha1', 'a'), ('is_exec', mode, False),
1193
def test_update_entry_no_stat_value(self):
1194
"""Passing the stat_value is optional."""
1195
state, entry = self.get_state_with_a()
1196
state.adjust_time(-10) # Make sure the file looks new
1197
self.build_tree(['a'])
1198
# Add one where we don't provide the stat or sha already
1199
link_or_sha1 = state.update_entry(entry, abspath='a')
1200
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1202
stat_value = os.lstat('a')
1203
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1204
('is_exec', stat_value.st_mode, False),
1207
def test_update_entry_symlink(self):
1208
"""Update entry should read symlinks."""
1209
if not osutils.has_symlinks():
1210
# PlatformDeficiency / TestSkipped
1211
raise TestSkipped("No symlink support")
1212
state, entry = self.get_state_with_a()
1214
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1215
state._dirblock_state)
1216
os.symlink('target', 'a')
1218
state.adjust_time(-10) # Make the symlink look new
1219
stat_value = os.lstat('a')
1220
packed_stat = dirstate.pack_stat(stat_value)
1221
link_or_sha1 = state.update_entry(entry, abspath='a',
1222
stat_value=stat_value)
1223
self.assertEqual('target', link_or_sha1)
1224
self.assertEqual([('read_link', 'a', '')], state._log)
1225
# Dirblock is updated
1226
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1228
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1229
state._dirblock_state)
1231
# Because the stat_value looks new, we should re-read the target
1232
link_or_sha1 = state.update_entry(entry, abspath='a',
1233
stat_value=stat_value)
1234
self.assertEqual('target', link_or_sha1)
1235
self.assertEqual([('read_link', 'a', ''),
1236
('read_link', 'a', 'target'),
1238
state.adjust_time(+20) # Skip into the future, all files look old
1239
link_or_sha1 = state.update_entry(entry, abspath='a',
1240
stat_value=stat_value)
1241
self.assertEqual('target', link_or_sha1)
1242
# There should not be a new read_link call.
1243
# (this is a weak assertion, because read_link is fairly inexpensive,
1244
# versus the number of symlinks that we would have)
1245
self.assertEqual([('read_link', 'a', ''),
1246
('read_link', 'a', 'target'),
1249
def test_update_entry_dir(self):
1250
state, entry = self.get_state_with_a()
1251
self.build_tree(['a/'])
1252
self.assertIs(None, state.update_entry(entry, 'a'))
1254
def create_and_test_file(self, state, entry):
1255
"""Create a file at 'a' and verify the state finds it.
1257
The state should already be versioning *something* at 'a'. This makes
1258
sure that state.update_entry recognizes it as a file.
1260
self.build_tree(['a'])
1261
stat_value = os.lstat('a')
1262
packed_stat = dirstate.pack_stat(stat_value)
1264
link_or_sha1 = state.update_entry(entry, abspath='a')
1265
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1267
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1271
def create_and_test_dir(self, state, entry):
1272
"""Create a directory at 'a' and verify the state finds it.
1274
The state should already be versioning *something* at 'a'. This makes
1275
sure that state.update_entry recognizes it as a directory.
1277
self.build_tree(['a/'])
1278
stat_value = os.lstat('a')
1279
packed_stat = dirstate.pack_stat(stat_value)
1281
link_or_sha1 = state.update_entry(entry, abspath='a')
1282
self.assertIs(None, link_or_sha1)
1283
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1287
def create_and_test_symlink(self, state, entry):
1288
"""Create a symlink at 'a' and verify the state finds it.
1290
The state should already be versioning *something* at 'a'. This makes
1291
sure that state.update_entry recognizes it as a symlink.
1293
This should not be called if this platform does not have symlink
1296
# caller should care about skipping test on platforms without symlinks
1297
os.symlink('path/to/foo', 'a')
1299
stat_value = os.lstat('a')
1300
packed_stat = dirstate.pack_stat(stat_value)
1302
link_or_sha1 = state.update_entry(entry, abspath='a')
1303
self.assertEqual('path/to/foo', link_or_sha1)
1304
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1308
def test_update_missing_file(self):
1309
state, entry = self.get_state_with_a()
1310
packed_stat = self.create_and_test_file(state, entry)
1311
# Now if we delete the file, update_entry should recover and
1314
self.assertIs(None, state.update_entry(entry, abspath='a'))
1315
# And the record shouldn't be changed.
1316
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1317
self.assertEqual([('f', digest, 14, False, packed_stat)],
1320
def test_update_missing_dir(self):
1321
state, entry = self.get_state_with_a()
1322
packed_stat = self.create_and_test_dir(state, entry)
1323
# Now if we delete the directory, update_entry should recover and
1326
self.assertIs(None, state.update_entry(entry, abspath='a'))
1327
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1329
def test_update_missing_symlink(self):
1330
if not osutils.has_symlinks():
1331
# PlatformDeficiency / TestSkipped
1332
raise TestSkipped("No symlink support")
1333
state, entry = self.get_state_with_a()
1334
packed_stat = self.create_and_test_symlink(state, entry)
1336
self.assertIs(None, state.update_entry(entry, abspath='a'))
1337
# And the record shouldn't be changed.
1338
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1341
def test_update_file_to_dir(self):
1342
"""If a file changes to a directory we return None for the sha.
1343
We also update the inventory record.
1345
state, entry = self.get_state_with_a()
1346
self.create_and_test_file(state, entry)
1348
self.create_and_test_dir(state, entry)
1350
def test_update_file_to_symlink(self):
1351
"""File becomes a symlink"""
1352
if not osutils.has_symlinks():
1353
# PlatformDeficiency / TestSkipped
1354
raise TestSkipped("No symlink support")
1355
state, entry = self.get_state_with_a()
1356
self.create_and_test_file(state, entry)
1358
self.create_and_test_symlink(state, entry)
1360
def test_update_dir_to_file(self):
1361
"""Directory becoming a file updates the entry."""
1362
state, entry = self.get_state_with_a()
1363
self.create_and_test_dir(state, entry)
1365
self.create_and_test_file(state, entry)
1367
def test_update_dir_to_symlink(self):
1368
"""Directory becomes a symlink"""
1369
if not osutils.has_symlinks():
1370
# PlatformDeficiency / TestSkipped
1371
raise TestSkipped("No symlink support")
1372
state, entry = self.get_state_with_a()
1373
self.create_and_test_dir(state, entry)
1375
self.create_and_test_symlink(state, entry)
1377
def test_update_symlink_to_file(self):
1378
"""Symlink becomes a file"""
1379
if not has_symlinks():
1380
raise TestSkipped("No symlink support")
1381
state, entry = self.get_state_with_a()
1382
self.create_and_test_symlink(state, entry)
1384
self.create_and_test_file(state, entry)
1386
def test_update_symlink_to_dir(self):
1387
"""Symlink becomes a directory"""
1388
if not has_symlinks():
1389
raise TestSkipped("No symlink support")
1390
state, entry = self.get_state_with_a()
1391
self.create_and_test_symlink(state, entry)
1393
self.create_and_test_dir(state, entry)
1395
def test__is_executable_win32(self):
1396
state, entry = self.get_state_with_a()
1397
self.build_tree(['a'])
1399
# Make sure we are using the win32 implementation of _is_executable
1400
state._is_executable = state._is_executable_win32
1402
# The file on disk is not executable, but we are marking it as though
1403
# it is. With _is_executable_win32 we ignore what is on disk.
1404
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1406
stat_value = os.lstat('a')
1407
packed_stat = dirstate.pack_stat(stat_value)
1409
state.adjust_time(-10) # Make sure everything is new
1410
# Make sure it wants to kkkkkkkk
1411
state.update_entry(entry, abspath='a', stat_value=stat_value)
1413
# The row is updated, but the executable bit stays set.
1414
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1415
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1418
class TestPackStat(TestCaseWithTransport):
1420
def assertPackStat(self, expected, stat_value):
1421
"""Check the packed and serialized form of a stat value."""
1422
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1424
def test_pack_stat_int(self):
1425
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1426
# Make sure that all parameters have an impact on the packed stat.
1427
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1430
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1431
st.st_mtime = 1172758620
1433
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1434
st.st_ctime = 1172758630
1436
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1439
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1440
st.st_ino = 6499540L
1442
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1443
st.st_mode = 0100744
1445
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1447
def test_pack_stat_float(self):
1448
"""On some platforms mtime and ctime are floats.
1450
Make sure we don't get warnings or errors, and that we ignore changes <
1453
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1454
777L, 6499538L, 0100644)
1455
# These should all be the same as the integer counterparts
1456
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1457
st.st_mtime = 1172758620.0
1459
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1460
st.st_ctime = 1172758630.0
1462
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1463
# fractional seconds are discarded, so no change from above
1464
st.st_mtime = 1172758620.453
1465
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1466
st.st_ctime = 1172758630.228
1467
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1470
class TestBisect(TestCaseWithTransport):
1471
"""Test the ability to bisect into the disk format."""
1473
def create_basic_dirstate(self):
1474
"""Create a dirstate with a few files and directories.
1483
tree = self.make_branch_and_tree('tree')
1484
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1485
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1486
self.build_tree(['tree/' + p for p in paths])
1487
tree.set_root_id('TREE_ROOT')
1488
tree.add([p.rstrip('/') for p in paths], file_ids)
1489
tree.commit('initial', rev_id='rev-1')
1490
revision_id = 'rev-1'
1491
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1492
t = self.get_transport().clone('tree')
1493
a_text = t.get_bytes('a')
1494
a_sha = osutils.sha_string(a_text)
1496
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1497
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1498
c_text = t.get_bytes('b/c')
1499
c_sha = osutils.sha_string(c_text)
1501
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1502
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1503
e_text = t.get_bytes('b/d/e')
1504
e_sha = osutils.sha_string(e_text)
1506
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1507
f_text = t.get_bytes('f')
1508
f_sha = osutils.sha_string(f_text)
1510
null_stat = dirstate.DirState.NULLSTAT
1512
'':(('', '', 'TREE_ROOT'), [
1513
('d', '', 0, False, null_stat),
1514
('d', '', 0, False, revision_id),
1516
'a':(('', 'a', 'a-id'), [
1517
('f', '', 0, False, null_stat),
1518
('f', a_sha, a_len, False, revision_id),
1520
'b':(('', 'b', 'b-id'), [
1521
('d', '', 0, False, null_stat),
1522
('d', '', 0, False, revision_id),
1524
'b/c':(('b', 'c', 'c-id'), [
1525
('f', '', 0, False, null_stat),
1526
('f', c_sha, c_len, False, revision_id),
1528
'b/d':(('b', 'd', 'd-id'), [
1529
('d', '', 0, False, null_stat),
1530
('d', '', 0, False, revision_id),
1532
'b/d/e':(('b/d', 'e', 'e-id'), [
1533
('f', '', 0, False, null_stat),
1534
('f', e_sha, e_len, False, revision_id),
1536
'f':(('', 'f', 'f-id'), [
1537
('f', '', 0, False, null_stat),
1538
('f', f_sha, f_len, False, revision_id),
1541
state = dirstate.DirState.from_tree(tree, 'dirstate')
1546
# Use a different object, to make sure nothing is pre-cached in memory.
1547
state = dirstate.DirState.on_file('dirstate')
1549
self.addCleanup(state.unlock)
1550
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1551
state._dirblock_state)
1552
# This is code is only really tested if we actually have to make more
1553
# than one read, so set the page size to something smaller.
1554
# We want it to contain about 2.2 records, so that we have a couple
1555
# records that we can read per attempt
1556
state._bisect_page_size = 200
1557
return tree, state, expected
1559
def create_duplicated_dirstate(self):
1560
"""Create a dirstate with a deleted and added entries.
1562
This grabs a basic_dirstate, and then removes and re adds every entry
1565
tree, state, expected = self.create_basic_dirstate()
1566
# Now we will just remove and add every file so we get an extra entry
1567
# per entry. Unversion in reverse order so we handle subdirs
1568
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1569
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1570
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1572
# Update the expected dictionary.
1573
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1574
orig = expected[path]
1576
# This record was deleted in the current tree
1577
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1579
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1580
# And didn't exist in the basis tree
1581
expected[path2] = (new_key, [orig[1][0],
1582
dirstate.DirState.NULL_PARENT_DETAILS])
1584
# We will replace the 'dirstate' file underneath 'state', but that is
1585
# okay as lock as we unlock 'state' first.
1588
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1594
# But we need to leave state in a read-lock because we already have
1595
# a cleanup scheduled
1597
return tree, state, expected
1599
def create_renamed_dirstate(self):
1600
"""Create a dirstate with a few internal renames.
1602
This takes the basic dirstate, and moves the paths around.
1604
tree, state, expected = self.create_basic_dirstate()
1606
tree.rename_one('a', 'b/g')
1608
tree.rename_one('b/d', 'h')
1610
old_a = expected['a']
1611
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1612
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1613
('r', 'a', 0, False, '')])
1614
old_d = expected['b/d']
1615
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1616
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1617
('r', 'b/d', 0, False, '')])
1619
old_e = expected['b/d/e']
1620
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1622
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1623
('r', 'b/d/e', 0, False, '')])
1627
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1634
return tree, state, expected
1636
def assertBisect(self, expected_map, map_keys, state, paths):
1637
"""Assert that bisecting for paths returns the right result.
1639
:param expected_map: A map from key => entry value
1640
:param map_keys: The keys to expect for each path
1641
:param state: The DirState object.
1642
:param paths: A list of paths, these will automatically be split into
1643
(dir, name) tuples, and sorted according to how _bisect
1646
dir_names = sorted(osutils.split(p) for p in paths)
1647
result = state._bisect(dir_names)
1648
# For now, results are just returned in whatever order we read them.
1649
# We could sort by (dir, name, file_id) or something like that, but in
1650
# the end it would still be fairly arbitrary, and we don't want the
1651
# extra overhead if we can avoid it. So sort everything to make sure
1653
assert len(map_keys) == len(dir_names)
1655
for dir_name, keys in zip(dir_names, map_keys):
1657
# This should not be present in the output
1659
expected[dir_name] = sorted(expected_map[k] for k in keys)
1661
for dir_name in result:
1662
result[dir_name].sort()
1664
self.assertEqual(expected, result)
1666
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1667
"""Assert that bisecting for dirbblocks returns the right result.
1669
:param expected_map: A map from key => expected values
1670
:param map_keys: A nested list of paths we expect to be returned.
1671
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1672
:param state: The DirState object.
1673
:param paths: A list of directories
1675
result = state._bisect_dirblocks(paths)
1676
assert len(map_keys) == len(paths)
1679
for path, keys in zip(paths, map_keys):
1681
# This should not be present in the output
1683
expected[path] = sorted(expected_map[k] for k in keys)
1687
self.assertEqual(expected, result)
1689
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1690
"""Assert the return value of a recursive bisection.
1692
:param expected_map: A map from key => entry value
1693
:param map_keys: A list of paths we expect to be returned.
1694
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1695
:param state: The DirState object.
1696
:param paths: A list of files and directories. It will be broken up
1697
into (dir, name) pairs and sorted before calling _bisect_recursive.
1700
for key in map_keys:
1701
entry = expected_map[key]
1702
dir_name_id, trees_info = entry
1703
expected[dir_name_id] = trees_info
1705
dir_names = sorted(osutils.split(p) for p in paths)
1706
result = state._bisect_recursive(dir_names)
1708
self.assertEqual(expected, result)
1710
def test_bisect_each(self):
1711
"""Find a single record using bisect."""
1712
tree, state, expected = self.create_basic_dirstate()
1714
# Bisect should return the rows for the specified files.
1715
self.assertBisect(expected, [['']], state, [''])
1716
self.assertBisect(expected, [['a']], state, ['a'])
1717
self.assertBisect(expected, [['b']], state, ['b'])
1718
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1719
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1720
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1721
self.assertBisect(expected, [['f']], state, ['f'])
1723
def test_bisect_multi(self):
1724
"""Bisect can be used to find multiple records at the same time."""
1725
tree, state, expected = self.create_basic_dirstate()
1726
# Bisect should be capable of finding multiple entries at the same time
1727
self.assertBisect(expected, [['a'], ['b'], ['f']],
1728
state, ['a', 'b', 'f'])
1729
# ('', 'f') sorts before the others
1730
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1731
state, ['b/d', 'b/d/e', 'f'])
1733
def test_bisect_one_page(self):
1734
"""Test bisect when there is only 1 page to read"""
1735
tree, state, expected = self.create_basic_dirstate()
1736
state._bisect_page_size = 5000
1737
self.assertBisect(expected,[['']], state, [''])
1738
self.assertBisect(expected,[['a']], state, ['a'])
1739
self.assertBisect(expected,[['b']], state, ['b'])
1740
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1741
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1742
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1743
self.assertBisect(expected,[['f']], state, ['f'])
1744
self.assertBisect(expected,[['a'], ['b'], ['f']],
1745
state, ['a', 'b', 'f'])
1746
# ('', 'f') sorts before the others
1747
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1748
state, ['b/d', 'b/d/e', 'f'])
1750
def test_bisect_duplicate_paths(self):
1751
"""When bisecting for a path, handle multiple entries."""
1752
tree, state, expected = self.create_duplicated_dirstate()
1754
# Now make sure that both records are properly returned.
1755
self.assertBisect(expected, [['']], state, [''])
1756
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1757
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1758
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1759
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1760
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1762
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1764
def test_bisect_page_size_too_small(self):
1765
"""If the page size is too small, we will auto increase it."""
1766
tree, state, expected = self.create_basic_dirstate()
1767
state._bisect_page_size = 50
1768
self.assertBisect(expected, [None], state, ['b/e'])
1769
self.assertBisect(expected, [['a']], state, ['a'])
1770
self.assertBisect(expected, [['b']], state, ['b'])
1771
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1772
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1773
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1774
self.assertBisect(expected, [['f']], state, ['f'])
1776
def test_bisect_missing(self):
1777
"""Test that bisect return None if it cannot find a path."""
1778
tree, state, expected = self.create_basic_dirstate()
1779
self.assertBisect(expected, [None], state, ['foo'])
1780
self.assertBisect(expected, [None], state, ['b/foo'])
1781
self.assertBisect(expected, [None], state, ['bar/foo'])
1783
self.assertBisect(expected, [['a'], None, ['b/d']],
1784
state, ['a', 'foo', 'b/d'])
1786
def test_bisect_rename(self):
1787
"""Check that we find a renamed row."""
1788
tree, state, expected = self.create_renamed_dirstate()
1790
# Search for the pre and post renamed entries
1791
self.assertBisect(expected, [['a']], state, ['a'])
1792
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1793
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1794
self.assertBisect(expected, [['h']], state, ['h'])
1796
# What about b/d/e? shouldn't that also get 2 directory entries?
1797
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1798
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1800
def test_bisect_dirblocks(self):
1801
tree, state, expected = self.create_duplicated_dirstate()
1802
self.assertBisectDirBlocks(expected,
1803
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1804
self.assertBisectDirBlocks(expected,
1805
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1806
self.assertBisectDirBlocks(expected,
1807
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1808
self.assertBisectDirBlocks(expected,
1809
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1810
['b/c', 'b/c2', 'b/d', 'b/d2'],
1811
['b/d/e', 'b/d/e2'],
1812
], state, ['', 'b', 'b/d'])
1814
def test_bisect_dirblocks_missing(self):
1815
tree, state, expected = self.create_basic_dirstate()
1816
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1817
state, ['b/d', 'b/e'])
1818
# Files don't show up in this search
1819
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1820
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1821
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1822
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1823
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1825
def test_bisect_recursive_each(self):
1826
tree, state, expected = self.create_basic_dirstate()
1827
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1828
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1829
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1830
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1832
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1834
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1838
def test_bisect_recursive_multiple(self):
1839
tree, state, expected = self.create_basic_dirstate()
1840
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1841
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1842
state, ['b/d', 'b/d/e'])
1844
def test_bisect_recursive_missing(self):
1845
tree, state, expected = self.create_basic_dirstate()
1846
self.assertBisectRecursive(expected, [], state, ['d'])
1847
self.assertBisectRecursive(expected, [], state, ['b/e'])
1848
self.assertBisectRecursive(expected, [], state, ['g'])
1849
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1851
def test_bisect_recursive_renamed(self):
1852
tree, state, expected = self.create_renamed_dirstate()
1854
# Looking for either renamed item should find the other
1855
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1856
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1857
# Looking in the containing directory should find the rename target,
1858
# and anything in a subdir of the renamed target.
1859
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1860
'b/d/e', 'b/g', 'h', 'h/e'],
1864
class TestBisectDirblock(TestCase):
1865
"""Test that bisect_dirblock() returns the expected values.
1867
bisect_dirblock is intended to work like bisect.bisect_left() except it
1868
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1869
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1872
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1873
"""Assert that bisect_split works like bisect_left on the split paths.
1875
:param dirblocks: A list of (path, [info]) pairs.
1876
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1877
:param path: The path we are indexing.
1879
All other arguments will be passed along.
1881
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1883
split_dirblock = (path.split('/'), [])
1884
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1886
self.assertEqual(bisect_left_idx, bisect_split_idx,
1887
'bisect_split disagreed. %s != %s'
1889
% (bisect_left_idx, bisect_split_idx, path)
1892
def paths_to_dirblocks(self, paths):
1893
"""Convert a list of paths into dirblock form.
1895
Also, ensure that the paths are in proper sorted order.
1897
dirblocks = [(path, []) for path in paths]
1898
split_dirblocks = [(path.split('/'), []) for path in paths]
1899
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1900
return dirblocks, split_dirblocks
1902
def test_simple(self):
1903
"""In the simple case it works just like bisect_left"""
1904
paths = ['', 'a', 'b', 'c', 'd']
1905
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1907
self.assertBisect(dirblocks, split_dirblocks, path)
1908
self.assertBisect(dirblocks, split_dirblocks, '_')
1909
self.assertBisect(dirblocks, split_dirblocks, 'aa')
1910
self.assertBisect(dirblocks, split_dirblocks, 'bb')
1911
self.assertBisect(dirblocks, split_dirblocks, 'cc')
1912
self.assertBisect(dirblocks, split_dirblocks, 'dd')
1913
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1914
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1915
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1916
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1918
def test_involved(self):
1919
"""This is where bisect_left diverges slightly."""
1921
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1922
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1924
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1925
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1928
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1930
self.assertBisect(dirblocks, split_dirblocks, path)
1932
def test_involved_cached(self):
1933
"""This is where bisect_left diverges slightly."""
1935
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1936
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1938
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1939
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1943
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1945
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)