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.tests import TestCase, TestCaseWithTransport
34
# general checks for NOT_IN_MEMORY error conditions.
35
# set_path_id on a NOT_IN_MEMORY dirstate
36
# set_path_id unicode support
37
# set_path_id setting id of a path not root
38
# set_path_id setting id when there are parents without the id in the parents
39
# set_path_id setting id when there are parents with the id in the parents
40
# set_path_id setting id when state is not in memory
41
# set_path_id setting id when state is in memory unmodified
42
# set_path_id setting id when state is in memory modified
45
class TestCaseWithDirState(TestCaseWithTransport):
46
"""Helper functions for creating DirState objects with various content."""
48
def create_empty_dirstate(self):
49
"""Return a locked but empty dirstate"""
50
state = dirstate.DirState.initialize('dirstate')
53
def create_dirstate_with_root(self):
54
"""Return a write-locked state with a single root entry."""
55
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
56
root_entry_direntry = ('', '', 'a-root-value'), [
57
('d', '', 0, False, packed_stat),
60
dirblocks.append(('', [root_entry_direntry]))
61
dirblocks.append(('', []))
62
state = self.create_empty_dirstate()
64
state._set_data([], dirblocks)
71
def create_dirstate_with_root_and_subdir(self):
72
"""Return a locked DirState with a root and a subdir"""
73
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
74
subdir_entry = ('', 'subdir', 'subdir-id'), [
75
('d', '', 0, False, packed_stat),
77
state = self.create_dirstate_with_root()
79
dirblocks = list(state._dirblocks)
80
dirblocks[1][1].append(subdir_entry)
81
state._set_data([], dirblocks)
87
def create_complex_dirstate(self):
88
"""This dirstate contains multiple files and directories.
98
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
100
# Notice that a/e is an empty directory.
102
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
103
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
104
root_entry = ('', '', 'a-root-value'), [
105
('d', '', 0, False, packed_stat),
107
a_entry = ('', 'a', 'a-dir'), [
108
('d', '', 0, False, packed_stat),
110
b_entry = ('', 'b', 'b-dir'), [
111
('d', '', 0, False, packed_stat),
113
c_entry = ('', 'c', 'c-file'), [
114
('f', null_sha, 10, False, packed_stat),
116
d_entry = ('', 'd', 'd-file'), [
117
('f', null_sha, 20, False, packed_stat),
119
e_entry = ('a', 'e', 'e-dir'), [
120
('d', '', 0, False, packed_stat),
122
f_entry = ('a', 'f', 'f-file'), [
123
('f', null_sha, 30, False, packed_stat),
125
g_entry = ('b', 'g', 'g-file'), [
126
('f', null_sha, 30, False, packed_stat),
128
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
129
('f', null_sha, 40, False, packed_stat),
132
dirblocks.append(('', [root_entry]))
133
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
134
dirblocks.append(('a', [e_entry, f_entry]))
135
dirblocks.append(('b', [g_entry, h_entry]))
136
state = dirstate.DirState.initialize('dirstate')
139
state._set_data([], dirblocks)
145
def check_state_with_reopen(self, expected_result, state):
146
"""Check that state has current state expected_result.
148
This will check the current state, open the file anew and check it
150
This function expects the current state to be locked for writing, and
151
will unlock it before re-opening.
152
This is required because we can't open a lock_read() while something
153
else has a lock_write().
154
write => mutually exclusive lock
157
# The state should already be write locked, since we just had to do
158
# some operation to get here.
159
assert state._lock_token is not None
161
self.assertEqual(expected_result[0], state.get_parent_ids())
162
# there should be no ghosts in this tree.
163
self.assertEqual([], state.get_ghosts())
164
# there should be one fileid in this tree - the root of the tree.
165
self.assertEqual(expected_result[1], list(state._iter_entries()))
169
del state # Callers should unlock
170
state = dirstate.DirState.on_file('dirstate')
173
self.assertEqual(expected_result[1], list(state._iter_entries()))
178
class TestTreeToDirState(TestCaseWithDirState):
180
def test_empty_to_dirstate(self):
181
"""We should be able to create a dirstate for an empty tree."""
182
# There are no files on disk and no parents
183
tree = self.make_branch_and_tree('tree')
184
expected_result = ([], [
185
(('', '', tree.path2id('')), # common details
186
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
188
state = dirstate.DirState.from_tree(tree, 'dirstate')
190
self.check_state_with_reopen(expected_result, state)
192
def test_1_parents_empty_to_dirstate(self):
193
# create a parent by doing a commit
194
tree = self.make_branch_and_tree('tree')
195
rev_id = tree.commit('first post').encode('utf8')
196
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
197
expected_result = ([rev_id], [
198
(('', '', tree.path2id('')), # common details
199
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
200
('d', '', 0, False, rev_id), # first parent details
202
state = dirstate.DirState.from_tree(tree, 'dirstate')
203
self.check_state_with_reopen(expected_result, state)
206
def test_2_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')
210
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
211
rev_id2 = tree2.commit('second post', allow_pointless=True)
212
tree.merge_from_branch(tree2.branch)
213
expected_result = ([rev_id, rev_id2], [
214
(('', '', tree.path2id('')), # common details
215
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
216
('d', '', 0, False, rev_id), # first parent details
217
('d', '', 0, False, rev_id2), # second parent details
219
state = dirstate.DirState.from_tree(tree, 'dirstate')
220
self.check_state_with_reopen(expected_result, state)
223
def test_empty_unknowns_are_ignored_to_dirstate(self):
224
"""We should be able to create a dirstate for an empty tree."""
225
# There are no files on disk and no parents
226
tree = self.make_branch_and_tree('tree')
227
self.build_tree(['tree/unknown'])
228
expected_result = ([], [
229
(('', '', tree.path2id('')), # common details
230
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
232
state = dirstate.DirState.from_tree(tree, 'dirstate')
233
self.check_state_with_reopen(expected_result, state)
235
def get_tree_with_a_file(self):
236
tree = self.make_branch_and_tree('tree')
237
self.build_tree(['tree/a file'])
238
tree.add('a file', 'a file id')
241
def test_non_empty_no_parents_to_dirstate(self):
242
"""We should be able to create a dirstate for an empty tree."""
243
# There are files on disk and no parents
244
tree = self.get_tree_with_a_file()
245
expected_result = ([], [
246
(('', '', tree.path2id('')), # common details
247
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
249
(('', 'a file', 'a file id'), # common
250
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
253
state = dirstate.DirState.from_tree(tree, 'dirstate')
254
self.check_state_with_reopen(expected_result, state)
256
def test_1_parents_not_empty_to_dirstate(self):
257
# create a parent by doing a commit
258
tree = self.get_tree_with_a_file()
259
rev_id = tree.commit('first post').encode('utf8')
260
# change the current content to be different this will alter stat, sha
262
self.build_tree_contents([('tree/a file', 'new content\n')])
263
expected_result = ([rev_id], [
264
(('', '', tree.path2id('')), # common details
265
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
266
('d', '', 0, False, rev_id), # first parent details
268
(('', 'a file', 'a file id'), # common
269
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
270
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
271
rev_id), # first parent
274
state = dirstate.DirState.from_tree(tree, 'dirstate')
275
self.check_state_with_reopen(expected_result, state)
277
def test_2_parents_not_empty_to_dirstate(self):
278
# create a parent by doing a commit
279
tree = self.get_tree_with_a_file()
280
rev_id = tree.commit('first post').encode('utf8')
281
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
282
# change the current content to be different this will alter stat, sha
284
self.build_tree_contents([('tree2/a file', 'merge content\n')])
285
rev_id2 = tree2.commit('second post').encode('utf8')
286
tree.merge_from_branch(tree2.branch)
287
# change the current content to be different this will alter stat, sha
288
# and length again, giving us three distinct values:
289
self.build_tree_contents([('tree/a file', 'new content\n')])
290
expected_result = ([rev_id, rev_id2], [
291
(('', '', tree.path2id('')), # common details
292
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
293
('d', '', 0, False, rev_id), # first parent details
294
('d', '', 0, False, rev_id2), # second parent details
296
(('', 'a file', 'a file id'), # common
297
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
298
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
299
rev_id), # first parent
300
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
301
rev_id2), # second parent
304
state = dirstate.DirState.from_tree(tree, 'dirstate')
305
self.check_state_with_reopen(expected_result, state)
307
def test_colliding_fileids(self):
308
# test insertion of parents creating several entries at the same path.
309
# we used to have a bug where they could cause the dirstate to break
310
# its ordering invariants.
311
# create some trees to test from
314
tree = self.make_branch_and_tree('tree%d' % i)
315
self.build_tree(['tree%d/name' % i,])
316
tree.add(['name'], ['file-id%d' % i])
317
revision_id = 'revid-%d' % i
318
tree.commit('message', rev_id=revision_id)
319
parents.append((revision_id,
320
tree.branch.repository.revision_tree(revision_id)))
321
# now fold these trees into a dirstate
322
state = dirstate.DirState.initialize('dirstate')
324
state.set_parent_trees(parents, [])
330
class TestDirStateOnFile(TestCaseWithDirState):
332
def test_construct_with_path(self):
333
tree = self.make_branch_and_tree('tree')
334
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
335
# we want to be able to get the lines of the dirstate that we will
337
lines = state.get_lines()
339
self.build_tree_contents([('dirstate', ''.join(lines))])
341
# no parents, default tree content
342
expected_result = ([], [
343
(('', '', tree.path2id('')), # common details
344
# current tree details, but new from_tree skips statting, it
345
# uses set_state_from_inventory, and thus depends on the
347
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
350
state = dirstate.DirState.on_file('dirstate')
351
state.lock_write() # check_state_with_reopen will save() and unlock it
352
self.check_state_with_reopen(expected_result, state)
354
def test_can_save_clean_on_file(self):
355
tree = self.make_branch_and_tree('tree')
356
state = dirstate.DirState.from_tree(tree, 'dirstate')
358
# doing a save should work here as there have been no changes.
360
# TODO: stat it and check it hasn't changed; may require waiting
361
# for the state accuracy window.
366
class TestDirStateInitialize(TestCaseWithDirState):
368
def test_initialize(self):
369
expected_result = ([], [
370
(('', '', 'TREE_ROOT'), # common details
371
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
374
state = dirstate.DirState.initialize('dirstate')
376
self.assertIsInstance(state, dirstate.DirState)
377
lines = state.get_lines()
378
self.assertFileEqual(''.join(state.get_lines()),
380
self.check_state_with_reopen(expected_result, state)
386
class TestDirStateManipulations(TestCaseWithDirState):
388
def test_set_state_from_inventory_no_content_no_parents(self):
389
# setting the current inventory is a slow but important api to support.
390
tree1 = self.make_branch_and_memory_tree('tree1')
394
revid1 = tree1.commit('foo').encode('utf8')
395
root_id = tree1.inventory.root.file_id
396
inv = tree1.inventory
399
expected_result = [], [
400
(('', '', root_id), [
401
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
402
state = dirstate.DirState.initialize('dirstate')
404
state.set_state_from_inventory(inv)
405
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
407
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
408
state._dirblock_state)
413
# This will unlock it
414
self.check_state_with_reopen(expected_result, state)
416
def test_set_path_id_no_parents(self):
417
"""The id of a path can be changed trivally with no parents."""
418
state = dirstate.DirState.initialize('dirstate')
420
# check precondition to be sure the state does change appropriately.
422
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
423
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
424
list(state._iter_entries()))
425
state.set_path_id('', 'foobarbaz')
427
(('', '', 'foobarbaz'), [('d', '', 0, False,
428
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
429
self.assertEqual(expected_rows, list(state._iter_entries()))
430
# should work across save too
434
state = dirstate.DirState.on_file('dirstate')
438
self.assertEqual(expected_rows, list(state._iter_entries()))
442
def test_set_path_id_with_parents(self):
443
"""Set the root file id in a dirstate with parents"""
444
mt = self.make_branch_and_tree('mt')
445
# in case the default tree format uses a different root id
446
mt.set_root_id('TREE_ROOT')
447
mt.commit('foo', rev_id='parent-revid')
448
rt = mt.branch.repository.revision_tree('parent-revid')
449
state = dirstate.DirState.initialize('dirstate')
452
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
453
state.set_path_id('', 'foobarbaz')
455
# now see that it is what we expected
457
(('', '', 'TREE_ROOT'),
458
[('a', '', 0, False, ''),
459
('d', '', 0, False, 'parent-revid'),
461
(('', '', 'foobarbaz'),
462
[('d', '', 0, False, ''),
463
('a', '', 0, False, ''),
467
self.assertEqual(expected_rows, list(state._iter_entries()))
468
# should work across save too
472
# now flush & check we get the same
473
state = dirstate.DirState.on_file('dirstate')
477
self.assertEqual(expected_rows, list(state._iter_entries()))
480
# now change within an existing file-backed state
484
state.set_path_id('', 'tree-root-2')
490
def test_set_parent_trees_no_content(self):
491
# set_parent_trees is a slow but important api to support.
492
tree1 = self.make_branch_and_memory_tree('tree1')
496
revid1 = tree1.commit('foo')
499
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
500
tree2 = MemoryTree.create_on_branch(branch2)
503
revid2 = tree2.commit('foo')
504
root_id = tree2.inventory.root.file_id
507
state = dirstate.DirState.initialize('dirstate')
509
state.set_path_id('', root_id)
510
state.set_parent_trees(
511
((revid1, tree1.branch.repository.revision_tree(revid1)),
512
(revid2, tree2.branch.repository.revision_tree(revid2)),
513
('ghost-rev', None)),
515
# check we can reopen and use the dirstate after setting parent
522
state = dirstate.DirState.on_file('dirstate')
525
self.assertEqual([revid1, revid2, 'ghost-rev'],
526
state.get_parent_ids())
527
# iterating the entire state ensures that the state is parsable.
528
list(state._iter_entries())
529
# be sure that it sets not appends - change it
530
state.set_parent_trees(
531
((revid1, tree1.branch.repository.revision_tree(revid1)),
532
('ghost-rev', None)),
534
# and now put it back.
535
state.set_parent_trees(
536
((revid1, tree1.branch.repository.revision_tree(revid1)),
537
(revid2, tree2.branch.repository.revision_tree(revid2)),
538
('ghost-rev', tree2.branch.repository.revision_tree(None))),
540
self.assertEqual([revid1, revid2, 'ghost-rev'],
541
state.get_parent_ids())
542
# the ghost should be recorded as such by set_parent_trees.
543
self.assertEqual(['ghost-rev'], state.get_ghosts())
545
[(('', '', root_id), [
546
('d', '', 0, False, dirstate.DirState.NULLSTAT),
547
('d', '', 0, False, revid1),
548
('d', '', 0, False, revid2)
550
list(state._iter_entries()))
554
def test_set_parent_trees_file_missing_from_tree(self):
555
# Adding a parent tree may reference files not in the current state.
556
# they should get listed just once by id, even if they are in two
558
# set_parent_trees is a slow but important api to support.
559
tree1 = self.make_branch_and_memory_tree('tree1')
563
tree1.add(['a file'], ['file-id'], ['file'])
564
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
565
revid1 = tree1.commit('foo')
568
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
569
tree2 = MemoryTree.create_on_branch(branch2)
572
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
573
revid2 = tree2.commit('foo')
574
root_id = tree2.inventory.root.file_id
577
# check the layout in memory
578
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
579
(('', '', root_id), [
580
('d', '', 0, False, dirstate.DirState.NULLSTAT),
581
('d', '', 0, False, revid1.encode('utf8')),
582
('d', '', 0, False, revid2.encode('utf8'))
584
(('', 'a file', 'file-id'), [
585
('a', '', 0, False, ''),
586
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
587
revid1.encode('utf8')),
588
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
589
revid2.encode('utf8'))
592
state = dirstate.DirState.initialize('dirstate')
594
state.set_path_id('', root_id)
595
state.set_parent_trees(
596
((revid1, tree1.branch.repository.revision_tree(revid1)),
597
(revid2, tree2.branch.repository.revision_tree(revid2)),
603
# check_state_with_reopen will unlock
604
self.check_state_with_reopen(expected_result, state)
606
### add a path via _set_data - so we dont need delta work, just
607
# raw data in, and ensure that it comes out via get_lines happily.
609
def test_add_path_to_root_no_parents_all_data(self):
610
# The most trivial addition of a path is when there are no parents and
611
# its in the root and all data about the file is supplied
612
self.build_tree(['a file'])
613
stat = os.lstat('a file')
614
# the 1*20 is the sha1 pretend value.
615
state = dirstate.DirState.initialize('dirstate')
617
(('', '', 'TREE_ROOT'), [
618
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
620
(('', 'a file', 'a file id'), [
621
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
625
state.add('a file', 'a file id', 'file', stat, '1'*20)
626
# having added it, it should be in the output of iter_entries.
627
self.assertEqual(expected_entries, list(state._iter_entries()))
628
# saving and reloading should not affect this.
632
state = dirstate.DirState.on_file('dirstate')
635
self.assertEqual(expected_entries, list(state._iter_entries()))
639
def test_add_path_to_unversioned_directory(self):
640
"""Adding a path to an unversioned directory should error.
642
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
643
once dirstate is stable and if it is merged with WorkingTree3, consider
644
removing this copy of the test.
646
self.build_tree(['unversioned/', 'unversioned/a file'])
647
state = dirstate.DirState.initialize('dirstate')
649
self.assertRaises(errors.NotVersionedError, state.add,
650
'unversioned/a file', 'a file id', 'file', None, None)
654
def test_add_directory_to_root_no_parents_all_data(self):
655
# The most trivial addition of a dir is when there are no parents and
656
# its in the root and all data about the file is supplied
657
self.build_tree(['a dir/'])
658
stat = os.lstat('a dir')
660
(('', '', 'TREE_ROOT'), [
661
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
663
(('', 'a dir', 'a dir id'), [
664
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
667
state = dirstate.DirState.initialize('dirstate')
669
state.add('a dir', 'a dir id', 'directory', stat, None)
670
# having added it, it should be in the output of iter_entries.
671
self.assertEqual(expected_entries, list(state._iter_entries()))
672
# saving and reloading should not affect this.
676
state = dirstate.DirState.on_file('dirstate')
680
self.assertEqual(expected_entries, list(state._iter_entries()))
684
def test_add_symlink_to_root_no_parents_all_data(self):
685
# The most trivial addition of a symlink when there are no parents and
686
# its in the root and all data about the file is supplied
687
## TODO: windows: dont fail this test. Also, how are symlinks meant to
688
# be represented on windows.
689
os.symlink('target', 'a link')
690
stat = os.lstat('a link')
692
(('', '', 'TREE_ROOT'), [
693
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
695
(('', 'a link', 'a link id'), [
696
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
699
state = dirstate.DirState.initialize('dirstate')
701
state.add('a link', 'a link id', 'symlink', stat, 'target')
702
# having added it, it should be in the output of iter_entries.
703
self.assertEqual(expected_entries, list(state._iter_entries()))
704
# saving and reloading should not affect this.
708
state = dirstate.DirState.on_file('dirstate')
711
self.assertEqual(expected_entries, list(state._iter_entries()))
715
def test_add_directory_and_child_no_parents_all_data(self):
716
# after adding a directory, we should be able to add children to it.
717
self.build_tree(['a dir/', 'a dir/a file'])
718
dirstat = os.lstat('a dir')
719
filestat = os.lstat('a dir/a file')
721
(('', '', 'TREE_ROOT'), [
722
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
724
(('', 'a dir', 'a dir id'), [
725
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
727
(('a dir', 'a file', 'a file id'), [
728
('f', '1'*20, 25, False,
729
dirstate.pack_stat(filestat)), # current tree details
732
state = dirstate.DirState.initialize('dirstate')
734
state.add('a dir', 'a dir id', 'directory', dirstat, None)
735
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
736
# added it, it should be in the output of iter_entries.
737
self.assertEqual(expected_entries, list(state._iter_entries()))
738
# saving and reloading should not affect this.
742
state = dirstate.DirState.on_file('dirstate')
745
self.assertEqual(expected_entries, list(state._iter_entries()))
749
def test_add_tree_reference(self):
750
# make a dirstate and add a tree reference
751
state = dirstate.DirState.initialize('dirstate')
753
('', 'subdir', 'subdir-id'),
754
[('t', 'subtree-123123', 0, False,
755
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
758
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
759
entry = state._get_entry(0, 'subdir-id', 'subdir')
760
self.assertEqual(entry, expected_entry)
765
# now check we can read it back
769
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
770
self.assertEqual(entry, entry2)
771
self.assertEqual(entry, expected_entry)
772
# and lookup by id should work too
773
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
774
self.assertEqual(entry, expected_entry)
778
def test_add_forbidden_names(self):
779
state = dirstate.DirState.initialize('dirstate')
780
self.addCleanup(state.unlock)
781
self.assertRaises(errors.BzrError,
782
state.add, '.', 'ass-id', 'directory', None, None)
783
self.assertRaises(errors.BzrError,
784
state.add, '..', 'ass-id', 'directory', None, None)
787
class TestGetLines(TestCaseWithDirState):
789
def test_get_line_with_2_rows(self):
790
state = self.create_dirstate_with_root_and_subdir()
792
self.assertEqual(['#bazaar dirstate flat format 3\n',
797
'\x00\x00a-root-value\x00'
798
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
799
'\x00subdir\x00subdir-id\x00'
800
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
801
], state.get_lines())
805
def test_entry_to_line(self):
806
state = self.create_dirstate_with_root()
809
'\x00\x00a-root-value\x00d\x00\x000\x00n'
810
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
811
state._entry_to_line(state._dirblocks[0][1][0]))
815
def test_entry_to_line_with_parent(self):
816
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
817
root_entry = ('', '', 'a-root-value'), [
818
('d', '', 0, False, packed_stat), # current tree details
819
# first: a pointer to the current location
820
('a', 'dirname/basename', 0, False, ''),
822
state = dirstate.DirState.initialize('dirstate')
825
'\x00\x00a-root-value\x00'
826
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
827
'a\x00dirname/basename\x000\x00n\x00',
828
state._entry_to_line(root_entry))
832
def test_entry_to_line_with_two_parents_at_different_paths(self):
833
# / in the tree, at / in one parent and /dirname/basename in the other.
834
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
835
root_entry = ('', '', 'a-root-value'), [
836
('d', '', 0, False, packed_stat), # current tree details
837
('d', '', 0, False, 'rev_id'), # first parent details
838
# second: a pointer to the current location
839
('a', 'dirname/basename', 0, False, ''),
841
state = dirstate.DirState.initialize('dirstate')
844
'\x00\x00a-root-value\x00'
845
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
846
'd\x00\x000\x00n\x00rev_id\x00'
847
'a\x00dirname/basename\x000\x00n\x00',
848
state._entry_to_line(root_entry))
852
def test_iter_entries(self):
853
# we should be able to iterate the dirstate entries from end to end
854
# this is for get_lines to be easy to read.
855
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
857
root_entries = [(('', '', 'a-root-value'), [
858
('d', '', 0, False, packed_stat), # current tree details
860
dirblocks.append(('', root_entries))
861
# add two files in the root
862
subdir_entry = ('', 'subdir', 'subdir-id'), [
863
('d', '', 0, False, packed_stat), # current tree details
865
afile_entry = ('', 'afile', 'afile-id'), [
866
('f', 'sha1value', 34, False, packed_stat), # current tree details
868
dirblocks.append(('', [subdir_entry, afile_entry]))
870
file_entry2 = ('subdir', '2file', '2file-id'), [
871
('f', 'sha1value', 23, False, packed_stat), # current tree details
873
dirblocks.append(('subdir', [file_entry2]))
874
state = dirstate.DirState.initialize('dirstate')
876
state._set_data([], dirblocks)
877
expected_entries = [root_entries[0], subdir_entry, afile_entry,
879
self.assertEqual(expected_entries, list(state._iter_entries()))
884
class TestGetBlockRowIndex(TestCaseWithDirState):
886
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
887
file_present, state, dirname, basename, tree_index):
888
self.assertEqual((block_index, row_index, dir_present, file_present),
889
state._get_block_entry_index(dirname, basename, tree_index))
891
block = state._dirblocks[block_index]
892
self.assertEqual(dirname, block[0])
893
if dir_present and file_present:
894
row = state._dirblocks[block_index][1][row_index]
895
self.assertEqual(dirname, row[0][0])
896
self.assertEqual(basename, row[0][1])
898
def test_simple_structure(self):
899
state = self.create_dirstate_with_root_and_subdir()
900
self.addCleanup(state.unlock)
901
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
902
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
903
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
904
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
905
self.assertBlockRowIndexEqual(2, 0, False, False, state,
908
def test_complex_structure_exists(self):
909
state = self.create_complex_dirstate()
910
self.addCleanup(state.unlock)
911
# Make sure we can find everything that exists
912
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
913
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
914
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
915
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
916
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
917
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
918
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
919
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
920
self.assertBlockRowIndexEqual(3, 1, True, True, state,
923
def test_complex_structure_missing(self):
924
state = self.create_complex_dirstate()
925
self.addCleanup(state.unlock)
926
# Make sure things would be inserted in the right locations
927
# '_' comes before 'a'
928
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
929
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
930
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
931
self.assertBlockRowIndexEqual(1, 4, True, False, state,
933
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
934
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
935
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
936
# This would be inserted between a/ and b/
937
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
939
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
942
class TestGetEntry(TestCaseWithDirState):
944
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
945
"""Check that the right entry is returned for a request to getEntry."""
946
entry = state._get_entry(index, path_utf8=path)
948
self.assertEqual((None, None), entry)
951
self.assertEqual((dirname, basename, file_id), cur[:3])
953
def test_simple_structure(self):
954
state = self.create_dirstate_with_root_and_subdir()
955
self.addCleanup(state.unlock)
956
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
957
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
958
self.assertEntryEqual(None, None, None, state, 'missing', 0)
959
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
960
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
962
def test_complex_structure_exists(self):
963
state = self.create_complex_dirstate()
964
self.addCleanup(state.unlock)
965
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
966
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
967
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
968
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
969
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
970
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
971
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
972
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
973
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
976
def test_complex_structure_missing(self):
977
state = self.create_complex_dirstate()
978
self.addCleanup(state.unlock)
979
self.assertEntryEqual(None, None, None, state, '_', 0)
980
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
981
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
982
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
984
def test_get_entry_uninitialized(self):
985
"""Calling get_entry will load data if it needs to"""
986
state = self.create_dirstate_with_root()
992
state = dirstate.DirState.on_file('dirstate')
995
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
997
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
998
state._dirblock_state)
999
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1004
class TestDirstateSortOrder(TestCaseWithTransport):
1005
"""Test that DirState adds entries in the right order."""
1007
def test_add_sorting(self):
1008
"""Add entries in lexicographical order, we get path sorted order.
1010
This tests it to a depth of 4, to make sure we don't just get it right
1011
at a single depth. 'a/a' should come before 'a-a', even though it
1012
doesn't lexicographically.
1014
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1015
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1018
state = dirstate.DirState.initialize('dirstate')
1019
self.addCleanup(state.unlock)
1021
fake_stat = os.stat('dirstate')
1023
d_id = d.replace('/', '_')+'-id'
1024
file_path = d + '/f'
1025
file_id = file_path.replace('/', '_')+'-id'
1026
state.add(d, d_id, 'directory', fake_stat, null_sha)
1027
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1029
expected = ['', '', 'a',
1030
'a/a', 'a/a/a', 'a/a/a/a',
1031
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1033
split = lambda p:p.split('/')
1034
self.assertEqual(sorted(expected, key=split), expected)
1035
dirblock_names = [d[0] for d in state._dirblocks]
1036
self.assertEqual(expected, dirblock_names)
1038
def test_set_parent_trees_correct_order(self):
1039
"""After calling set_parent_trees() we should maintain the order."""
1040
dirs = ['a', 'a-a', 'a/a']
1042
state = dirstate.DirState.initialize('dirstate')
1043
self.addCleanup(state.unlock)
1045
fake_stat = os.stat('dirstate')
1047
d_id = d.replace('/', '_')+'-id'
1048
file_path = d + '/f'
1049
file_id = file_path.replace('/', '_')+'-id'
1050
state.add(d, d_id, 'directory', fake_stat, null_sha)
1051
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1053
expected = ['', '', 'a', 'a/a', 'a-a']
1054
dirblock_names = [d[0] for d in state._dirblocks]
1055
self.assertEqual(expected, dirblock_names)
1057
# *really* cheesy way to just get an empty tree
1058
repo = self.make_repository('repo')
1059
empty_tree = repo.revision_tree(None)
1060
state.set_parent_trees([('null:', empty_tree)], [])
1062
dirblock_names = [d[0] for d in state._dirblocks]
1063
self.assertEqual(expected, dirblock_names)
1066
class InstrumentedDirState(dirstate.DirState):
1067
"""An DirState with instrumented sha1 functionality."""
1069
def __init__(self, path):
1070
super(InstrumentedDirState, self).__init__(path)
1071
self._time_offset = 0
1074
def _sha_cutoff_time(self):
1075
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1076
self._cutoff_time = timestamp + self._time_offset
1078
def _sha1_file(self, abspath, entry):
1079
self._log.append(('sha1', abspath))
1080
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1082
def _read_link(self, abspath, old_link):
1083
self._log.append(('read_link', abspath, old_link))
1084
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1086
def _lstat(self, abspath, entry):
1087
self._log.append(('lstat', abspath))
1088
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1090
def _is_executable(self, mode, old_executable):
1091
self._log.append(('is_exec', mode, old_executable))
1092
return super(InstrumentedDirState, self)._is_executable(mode,
1095
def adjust_time(self, secs):
1096
"""Move the clock forward or back.
1098
:param secs: The amount to adjust the clock by. Positive values make it
1099
seem as if we are in the future, negative values make it seem like we
1102
self._time_offset += secs
1103
self._cutoff_time = None
1106
class _FakeStat(object):
1107
"""A class with the same attributes as a real stat result."""
1109
def __init__(self, size, mtime, ctime, dev, ino, mode):
1111
self.st_mtime = mtime
1112
self.st_ctime = ctime
1118
class TestUpdateEntry(TestCaseWithDirState):
1119
"""Test the DirState.update_entry functions"""
1121
def get_state_with_a(self):
1122
"""Create a DirState tracking a single object named 'a'"""
1123
state = InstrumentedDirState.initialize('dirstate')
1124
self.addCleanup(state.unlock)
1125
state.add('a', 'a-id', 'file', None, '')
1126
entry = state._get_entry(0, path_utf8='a')
1129
def test_update_entry(self):
1130
state, entry = self.get_state_with_a()
1131
self.build_tree(['a'])
1132
# Add one where we don't provide the stat or sha already
1133
self.assertEqual(('', 'a', 'a-id'), entry[0])
1134
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1136
# Flush the buffers to disk
1138
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1139
state._dirblock_state)
1141
stat_value = os.lstat('a')
1142
packed_stat = dirstate.pack_stat(stat_value)
1143
link_or_sha1 = state.update_entry(entry, abspath='a',
1144
stat_value=stat_value)
1145
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1148
# The dirblock entry should be updated with the new info
1149
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1151
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1152
state._dirblock_state)
1153
mode = stat_value.st_mode
1154
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1157
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1158
state._dirblock_state)
1160
# If we do it again right away, we don't know if the file has changed
1161
# so we will re-read the file. Roll the clock back so the file is
1162
# guaranteed to look too new.
1163
state.adjust_time(-10)
1165
link_or_sha1 = state.update_entry(entry, abspath='a',
1166
stat_value=stat_value)
1167
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1168
('sha1', 'a'), ('is_exec', mode, False),
1170
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1172
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1173
state._dirblock_state)
1176
# However, if we move the clock forward so the file is considered
1177
# "stable", it should just returned the cached value.
1178
state.adjust_time(20)
1179
link_or_sha1 = state.update_entry(entry, abspath='a',
1180
stat_value=stat_value)
1181
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1183
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1184
('sha1', 'a'), ('is_exec', mode, False),
1187
def test_update_entry_no_stat_value(self):
1188
"""Passing the stat_value is optional."""
1189
state, entry = self.get_state_with_a()
1190
state.adjust_time(-10) # Make sure the file looks new
1191
self.build_tree(['a'])
1192
# Add one where we don't provide the stat or sha already
1193
link_or_sha1 = state.update_entry(entry, abspath='a')
1194
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1196
stat_value = os.lstat('a')
1197
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1198
('is_exec', stat_value.st_mode, False),
1201
def test_update_entry_symlink(self):
1202
"""Update entry should read symlinks."""
1203
if not osutils.has_symlinks():
1204
return # PlatformDeficiency / TestSkipped
1205
state, entry = self.get_state_with_a()
1207
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1208
state._dirblock_state)
1209
os.symlink('target', 'a')
1211
state.adjust_time(-10) # Make the symlink look new
1212
stat_value = os.lstat('a')
1213
packed_stat = dirstate.pack_stat(stat_value)
1214
link_or_sha1 = state.update_entry(entry, abspath='a',
1215
stat_value=stat_value)
1216
self.assertEqual('target', link_or_sha1)
1217
self.assertEqual([('read_link', 'a', '')], state._log)
1218
# Dirblock is updated
1219
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1221
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1222
state._dirblock_state)
1224
# Because the stat_value looks new, we should re-read the target
1225
link_or_sha1 = state.update_entry(entry, abspath='a',
1226
stat_value=stat_value)
1227
self.assertEqual('target', link_or_sha1)
1228
self.assertEqual([('read_link', 'a', ''),
1229
('read_link', 'a', 'target'),
1231
state.adjust_time(+20) # Skip into the future, all files look old
1232
link_or_sha1 = state.update_entry(entry, abspath='a',
1233
stat_value=stat_value)
1234
self.assertEqual('target', link_or_sha1)
1235
# There should not be a new read_link call.
1236
# (this is a weak assertion, because read_link is fairly inexpensive,
1237
# versus the number of symlinks that we would have)
1238
self.assertEqual([('read_link', 'a', ''),
1239
('read_link', 'a', 'target'),
1242
def test_update_entry_dir(self):
1243
state, entry = self.get_state_with_a()
1244
self.build_tree(['a/'])
1245
self.assertIs(None, state.update_entry(entry, 'a'))
1247
def create_and_test_file(self, state, entry):
1248
"""Create a file at 'a' and verify the state finds it.
1250
The state should already be versioning *something* at 'a'. This makes
1251
sure that state.update_entry recognizes it as a file.
1253
self.build_tree(['a'])
1254
stat_value = os.lstat('a')
1255
packed_stat = dirstate.pack_stat(stat_value)
1257
link_or_sha1 = state.update_entry(entry, abspath='a')
1258
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1260
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1264
def create_and_test_dir(self, state, entry):
1265
"""Create a directory at 'a' and verify the state finds it.
1267
The state should already be versioning *something* at 'a'. This makes
1268
sure that state.update_entry recognizes it as a directory.
1270
self.build_tree(['a/'])
1271
stat_value = os.lstat('a')
1272
packed_stat = dirstate.pack_stat(stat_value)
1274
link_or_sha1 = state.update_entry(entry, abspath='a')
1275
self.assertIs(None, link_or_sha1)
1276
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1280
def create_and_test_symlink(self, state, entry):
1281
"""Create a symlink at 'a' and verify the state finds it.
1283
The state should already be versioning *something* at 'a'. This makes
1284
sure that state.update_entry recognizes it as a symlink.
1286
This should not be called if this platform does not have symlink
1289
os.symlink('path/to/foo', 'a')
1291
stat_value = os.lstat('a')
1292
packed_stat = dirstate.pack_stat(stat_value)
1294
link_or_sha1 = state.update_entry(entry, abspath='a')
1295
self.assertEqual('path/to/foo', link_or_sha1)
1296
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1300
def test_update_missing_file(self):
1301
state, entry = self.get_state_with_a()
1302
packed_stat = self.create_and_test_file(state, entry)
1303
# Now if we delete the file, update_entry should recover and
1306
self.assertIs(None, state.update_entry(entry, abspath='a'))
1307
# And the record shouldn't be changed.
1308
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1309
self.assertEqual([('f', digest, 14, False, packed_stat)],
1312
def test_update_missing_dir(self):
1313
state, entry = self.get_state_with_a()
1314
packed_stat = self.create_and_test_dir(state, entry)
1315
# Now if we delete the directory, update_entry should recover and
1318
self.assertIs(None, state.update_entry(entry, abspath='a'))
1319
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1321
def test_update_missing_symlink(self):
1322
if not osutils.has_symlinks():
1323
return # PlatformDeficiency / TestSkipped
1324
state, entry = self.get_state_with_a()
1325
packed_stat = self.create_and_test_symlink(state, entry)
1327
self.assertIs(None, state.update_entry(entry, abspath='a'))
1328
# And the record shouldn't be changed.
1329
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1332
def test_update_file_to_dir(self):
1333
"""If a file changes to a directory we return None for the sha.
1334
We also update the inventory record.
1336
state, entry = self.get_state_with_a()
1337
self.create_and_test_file(state, entry)
1339
self.create_and_test_dir(state, entry)
1341
def test_update_file_to_symlink(self):
1342
"""File becomes a symlink"""
1343
if not osutils.has_symlinks():
1344
return # PlatformDeficiency / TestSkipped
1345
state, entry = self.get_state_with_a()
1346
self.create_and_test_file(state, entry)
1348
self.create_and_test_symlink(state, entry)
1350
def test_update_dir_to_file(self):
1351
"""Directory becoming a file updates the entry."""
1352
state, entry = self.get_state_with_a()
1353
self.create_and_test_dir(state, entry)
1355
self.create_and_test_file(state, entry)
1357
def test_update_dir_to_symlink(self):
1358
"""Directory becomes a symlink"""
1359
if not osutils.has_symlinks():
1360
return # PlatformDeficiency / TestSkipped
1361
state, entry = self.get_state_with_a()
1362
self.create_and_test_dir(state, entry)
1364
self.create_and_test_symlink(state, entry)
1366
def test_update_symlink_to_file(self):
1367
"""Symlink becomes a file"""
1368
state, entry = self.get_state_with_a()
1369
self.create_and_test_symlink(state, entry)
1371
self.create_and_test_file(state, entry)
1373
def test_update_symlink_to_dir(self):
1374
"""Symlink becomes a directory"""
1375
state, entry = self.get_state_with_a()
1376
self.create_and_test_symlink(state, entry)
1378
self.create_and_test_dir(state, entry)
1380
def test__is_executable_win32(self):
1381
state, entry = self.get_state_with_a()
1382
self.build_tree(['a'])
1384
# Make sure we are using the win32 implementation of _is_executable
1385
state._is_executable = state._is_executable_win32
1387
# The file on disk is not executable, but we are marking it as though
1388
# it is. With _is_executable_win32 we ignore what is on disk.
1389
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1391
stat_value = os.lstat('a')
1392
packed_stat = dirstate.pack_stat(stat_value)
1394
state.adjust_time(-10) # Make sure everything is new
1395
# Make sure it wants to kkkkkkkk
1396
state.update_entry(entry, abspath='a', stat_value=stat_value)
1398
# The row is updated, but the executable bit stays set.
1399
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1400
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1403
class TestPackStat(TestCaseWithTransport):
1405
def assertPackStat(self, expected, stat_value):
1406
"""Check the packed and serialized form of a stat value."""
1407
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1409
def test_pack_stat_int(self):
1410
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1411
# Make sure that all parameters have an impact on the packed stat.
1412
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1415
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1416
st.st_mtime = 1172758620
1418
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1419
st.st_ctime = 1172758630
1421
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1424
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1425
st.st_ino = 6499540L
1427
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1428
st.st_mode = 0100744
1430
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1432
def test_pack_stat_float(self):
1433
"""On some platforms mtime and ctime are floats.
1435
Make sure we don't get warnings or errors, and that we ignore changes <
1438
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1439
777L, 6499538L, 0100644)
1440
# These should all be the same as the integer counterparts
1441
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1442
st.st_mtime = 1172758620.0
1444
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1445
st.st_ctime = 1172758630.0
1447
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1448
# fractional seconds are discarded, so no change from above
1449
st.st_mtime = 1172758620.453
1450
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1451
st.st_ctime = 1172758630.228
1452
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1455
class TestBisect(TestCaseWithTransport):
1456
"""Test the ability to bisect into the disk format."""
1458
def create_basic_dirstate(self):
1459
"""Create a dirstate with a few files and directories.
1468
tree = self.make_branch_and_tree('tree')
1469
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1470
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1471
self.build_tree(['tree/' + p for p in paths])
1472
tree.set_root_id('TREE_ROOT')
1473
tree.add([p.rstrip('/') for p in paths], file_ids)
1474
tree.commit('initial', rev_id='rev-1')
1475
revision_id = 'rev-1'
1476
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1477
t = self.get_transport().clone('tree')
1478
a_text = t.get_bytes('a')
1479
a_sha = osutils.sha_string(a_text)
1481
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1482
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1483
c_text = t.get_bytes('b/c')
1484
c_sha = osutils.sha_string(c_text)
1486
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1487
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1488
e_text = t.get_bytes('b/d/e')
1489
e_sha = osutils.sha_string(e_text)
1491
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1492
f_text = t.get_bytes('f')
1493
f_sha = osutils.sha_string(f_text)
1495
null_stat = dirstate.DirState.NULLSTAT
1497
'':(('', '', 'TREE_ROOT'), [
1498
('d', '', 0, False, null_stat),
1499
('d', '', 0, False, revision_id),
1501
'a':(('', 'a', 'a-id'), [
1502
('f', '', 0, False, null_stat),
1503
('f', a_sha, a_len, False, revision_id),
1505
'b':(('', 'b', 'b-id'), [
1506
('d', '', 0, False, null_stat),
1507
('d', '', 0, False, revision_id),
1509
'b/c':(('b', 'c', 'c-id'), [
1510
('f', '', 0, False, null_stat),
1511
('f', c_sha, c_len, False, revision_id),
1513
'b/d':(('b', 'd', 'd-id'), [
1514
('d', '', 0, False, null_stat),
1515
('d', '', 0, False, revision_id),
1517
'b/d/e':(('b/d', 'e', 'e-id'), [
1518
('f', '', 0, False, null_stat),
1519
('f', e_sha, e_len, False, revision_id),
1521
'f':(('', 'f', 'f-id'), [
1522
('f', '', 0, False, null_stat),
1523
('f', f_sha, f_len, False, revision_id),
1526
state = dirstate.DirState.from_tree(tree, 'dirstate')
1531
# Use a different object, to make sure nothing is pre-cached in memory.
1532
state = dirstate.DirState.on_file('dirstate')
1534
self.addCleanup(state.unlock)
1535
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1536
state._dirblock_state)
1537
# This is code is only really tested if we actually have to make more
1538
# than one read, so set the page size to something smaller.
1539
# We want it to contain about 2.2 records, so that we have a couple
1540
# records that we can read per attempt
1541
state._bisect_page_size = 200
1542
return tree, state, expected
1544
def create_duplicated_dirstate(self):
1545
"""Create a dirstate with a deleted and added entries.
1547
This grabs a basic_dirstate, and then removes and re adds every entry
1550
tree, state, expected = self.create_basic_dirstate()
1551
# Now we will just remove and add every file so we get an extra entry
1552
# per entry. Unversion in reverse order so we handle subdirs
1553
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1554
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1555
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1557
# Update the expected dictionary.
1558
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1559
orig = expected[path]
1561
# This record was deleted in the current tree
1562
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1564
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1565
# And didn't exist in the basis tree
1566
expected[path2] = (new_key, [orig[1][0],
1567
dirstate.DirState.NULL_PARENT_DETAILS])
1569
# We will replace the 'dirstate' file underneath 'state', but that is
1570
# okay as lock as we unlock 'state' first.
1573
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1579
# But we need to leave state in a read-lock because we already have
1580
# a cleanup scheduled
1582
return tree, state, expected
1584
def create_renamed_dirstate(self):
1585
"""Create a dirstate with a few internal renames.
1587
This takes the basic dirstate, and moves the paths around.
1589
tree, state, expected = self.create_basic_dirstate()
1591
tree.rename_one('a', 'b/g')
1593
tree.rename_one('b/d', 'h')
1595
old_a = expected['a']
1596
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1597
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1598
('r', 'a', 0, False, '')])
1599
old_d = expected['b/d']
1600
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1601
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1602
('r', 'b/d', 0, False, '')])
1604
old_e = expected['b/d/e']
1605
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1607
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1608
('r', 'b/d/e', 0, False, '')])
1612
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1619
return tree, state, expected
1621
def assertBisect(self, expected_map, map_keys, state, paths):
1622
"""Assert that bisecting for paths returns the right result.
1624
:param expected_map: A map from key => entry value
1625
:param map_keys: The keys to expect for each path
1626
:param state: The DirState object.
1627
:param paths: A list of paths, these will automatically be split into
1628
(dir, name) tuples, and sorted according to how _bisect
1631
dir_names = sorted(osutils.split(p) for p in paths)
1632
result = state._bisect(dir_names)
1633
# For now, results are just returned in whatever order we read them.
1634
# We could sort by (dir, name, file_id) or something like that, but in
1635
# the end it would still be fairly arbitrary, and we don't want the
1636
# extra overhead if we can avoid it. So sort everything to make sure
1638
assert len(map_keys) == len(dir_names)
1640
for dir_name, keys in zip(dir_names, map_keys):
1642
# This should not be present in the output
1644
expected[dir_name] = sorted(expected_map[k] for k in keys)
1646
for dir_name in result:
1647
result[dir_name].sort()
1649
self.assertEqual(expected, result)
1651
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1652
"""Assert that bisecting for dirbblocks returns the right result.
1654
:param expected_map: A map from key => expected values
1655
:param map_keys: A nested list of paths we expect to be returned.
1656
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1657
:param state: The DirState object.
1658
:param paths: A list of directories
1660
result = state._bisect_dirblocks(paths)
1661
assert len(map_keys) == len(paths)
1664
for path, keys in zip(paths, map_keys):
1666
# This should not be present in the output
1668
expected[path] = sorted(expected_map[k] for k in keys)
1672
self.assertEqual(expected, result)
1674
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1675
"""Assert the return value of a recursive bisection.
1677
:param expected_map: A map from key => entry value
1678
:param map_keys: A list of paths we expect to be returned.
1679
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1680
:param state: The DirState object.
1681
:param paths: A list of files and directories. It will be broken up
1682
into (dir, name) pairs and sorted before calling _bisect_recursive.
1685
for key in map_keys:
1686
entry = expected_map[key]
1687
dir_name_id, trees_info = entry
1688
expected[dir_name_id] = trees_info
1690
dir_names = sorted(osutils.split(p) for p in paths)
1691
result = state._bisect_recursive(dir_names)
1693
self.assertEqual(expected, result)
1695
def test_bisect_each(self):
1696
"""Find a single record using bisect."""
1697
tree, state, expected = self.create_basic_dirstate()
1699
# Bisect should return the rows for the specified files.
1700
self.assertBisect(expected, [['']], state, [''])
1701
self.assertBisect(expected, [['a']], state, ['a'])
1702
self.assertBisect(expected, [['b']], state, ['b'])
1703
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1704
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1705
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1706
self.assertBisect(expected, [['f']], state, ['f'])
1708
def test_bisect_multi(self):
1709
"""Bisect can be used to find multiple records at the same time."""
1710
tree, state, expected = self.create_basic_dirstate()
1711
# Bisect should be capable of finding multiple entries at the same time
1712
self.assertBisect(expected, [['a'], ['b'], ['f']],
1713
state, ['a', 'b', 'f'])
1714
# ('', 'f') sorts before the others
1715
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1716
state, ['b/d', 'b/d/e', 'f'])
1718
def test_bisect_one_page(self):
1719
"""Test bisect when there is only 1 page to read"""
1720
tree, state, expected = self.create_basic_dirstate()
1721
state._bisect_page_size = 5000
1722
self.assertBisect(expected,[['']], state, [''])
1723
self.assertBisect(expected,[['a']], state, ['a'])
1724
self.assertBisect(expected,[['b']], state, ['b'])
1725
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1726
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1727
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1728
self.assertBisect(expected,[['f']], state, ['f'])
1729
self.assertBisect(expected,[['a'], ['b'], ['f']],
1730
state, ['a', 'b', 'f'])
1731
# ('', 'f') sorts before the others
1732
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1733
state, ['b/d', 'b/d/e', 'f'])
1735
def test_bisect_duplicate_paths(self):
1736
"""When bisecting for a path, handle multiple entries."""
1737
tree, state, expected = self.create_duplicated_dirstate()
1739
# Now make sure that both records are properly returned.
1740
self.assertBisect(expected, [['']], state, [''])
1741
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1742
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1743
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1744
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1745
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1747
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1749
def test_bisect_page_size_too_small(self):
1750
"""If the page size is too small, we will auto increase it."""
1751
tree, state, expected = self.create_basic_dirstate()
1752
state._bisect_page_size = 50
1753
self.assertBisect(expected, [None], state, ['b/e'])
1754
self.assertBisect(expected, [['a']], state, ['a'])
1755
self.assertBisect(expected, [['b']], state, ['b'])
1756
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1757
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1758
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1759
self.assertBisect(expected, [['f']], state, ['f'])
1761
def test_bisect_missing(self):
1762
"""Test that bisect return None if it cannot find a path."""
1763
tree, state, expected = self.create_basic_dirstate()
1764
self.assertBisect(expected, [None], state, ['foo'])
1765
self.assertBisect(expected, [None], state, ['b/foo'])
1766
self.assertBisect(expected, [None], state, ['bar/foo'])
1768
self.assertBisect(expected, [['a'], None, ['b/d']],
1769
state, ['a', 'foo', 'b/d'])
1771
def test_bisect_rename(self):
1772
"""Check that we find a renamed row."""
1773
tree, state, expected = self.create_renamed_dirstate()
1775
# Search for the pre and post renamed entries
1776
self.assertBisect(expected, [['a']], state, ['a'])
1777
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1778
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1779
self.assertBisect(expected, [['h']], state, ['h'])
1781
# What about b/d/e? shouldn't that also get 2 directory entries?
1782
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1783
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1785
def test_bisect_dirblocks(self):
1786
tree, state, expected = self.create_duplicated_dirstate()
1787
self.assertBisectDirBlocks(expected,
1788
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1789
self.assertBisectDirBlocks(expected,
1790
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1791
self.assertBisectDirBlocks(expected,
1792
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1793
self.assertBisectDirBlocks(expected,
1794
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1795
['b/c', 'b/c2', 'b/d', 'b/d2'],
1796
['b/d/e', 'b/d/e2'],
1797
], state, ['', 'b', 'b/d'])
1799
def test_bisect_dirblocks_missing(self):
1800
tree, state, expected = self.create_basic_dirstate()
1801
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1802
state, ['b/d', 'b/e'])
1803
# Files don't show up in this search
1804
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1805
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1806
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1807
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1808
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1810
def test_bisect_recursive_each(self):
1811
tree, state, expected = self.create_basic_dirstate()
1812
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1813
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1814
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1815
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1817
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1819
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1823
def test_bisect_recursive_multiple(self):
1824
tree, state, expected = self.create_basic_dirstate()
1825
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1826
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1827
state, ['b/d', 'b/d/e'])
1829
def test_bisect_recursive_missing(self):
1830
tree, state, expected = self.create_basic_dirstate()
1831
self.assertBisectRecursive(expected, [], state, ['d'])
1832
self.assertBisectRecursive(expected, [], state, ['b/e'])
1833
self.assertBisectRecursive(expected, [], state, ['g'])
1834
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1836
def test_bisect_recursive_renamed(self):
1837
tree, state, expected = self.create_renamed_dirstate()
1839
# Looking for either renamed item should find the other
1840
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1841
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1842
# Looking in the containing directory should find the rename target,
1843
# and anything in a subdir of the renamed target.
1844
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1845
'b/d/e', 'b/g', 'h', 'h/e'],
1849
class TestBisectDirblock(TestCase):
1850
"""Test that bisect_dirblock() returns the expected values.
1852
bisect_dirblock is intended to work like bisect.bisect_left() except it
1853
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1854
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1857
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1858
"""Assert that bisect_split works like bisect_left on the split paths.
1860
:param dirblocks: A list of (path, [info]) pairs.
1861
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1862
:param path: The path we are indexing.
1864
All other arguments will be passed along.
1866
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1868
split_dirblock = (path.split('/'), [])
1869
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1871
self.assertEqual(bisect_left_idx, bisect_split_idx,
1872
'bisect_split disagreed. %s != %s'
1874
% (bisect_left_idx, bisect_split_idx, path)
1877
def paths_to_dirblocks(self, paths):
1878
"""Convert a list of paths into dirblock form.
1880
Also, ensure that the paths are in proper sorted order.
1882
dirblocks = [(path, []) for path in paths]
1883
split_dirblocks = [(path.split('/'), []) for path in paths]
1884
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1885
return dirblocks, split_dirblocks
1887
def test_simple(self):
1888
"""In the simple case it works just like bisect_left"""
1889
paths = ['', 'a', 'b', 'c', 'd']
1890
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1892
self.assertBisect(dirblocks, split_dirblocks, path)
1893
self.assertBisect(dirblocks, split_dirblocks, '_')
1894
self.assertBisect(dirblocks, split_dirblocks, 'aa')
1895
self.assertBisect(dirblocks, split_dirblocks, 'bb')
1896
self.assertBisect(dirblocks, split_dirblocks, 'cc')
1897
self.assertBisect(dirblocks, split_dirblocks, 'dd')
1898
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1899
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1900
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1901
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1903
def test_involved(self):
1904
"""This is where bisect_left diverges slightly."""
1906
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1907
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1909
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1910
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1913
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1915
self.assertBisect(dirblocks, split_dirblocks, path)
1917
def test_involved_cached(self):
1918
"""This is where bisect_left diverges slightly."""
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',
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',
1928
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1930
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)