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
33
# test DirStateRevisionTree : test filtering out of deleted files does not
34
# filter out files called RECYCLED.BIN ;)
35
# test 0 parents, 1 parent, 4 parents.
36
# test unicode parents, non unicode parents
37
# test all change permutations in one and two parents.
38
# i.e. file in parent 1, dir in parent 2, symlink in tree.
39
# test that renames in the tree result in correct parent paths
40
# Test get state from a file, then asking for lines.
41
# write a smaller state, and check the file has been truncated.
42
# add a entry when its in state deleted
43
# revision attribute for root entries.
44
# test that utf8 strings are preserved in _row_to_line
45
# test parent manipulation
46
# test parents that are null in save : i.e. no record in the parent tree for this.
47
# todo: _set_data records ghost parents.
49
# general checks for NOT_IN_MEMORY error conditions.
50
# set_path_id on a NOT_IN_MEMORY dirstate
51
# set_path_id unicode support
52
# set_path_id setting id of a path not root
53
# set_path_id setting id when there are parents without the id in the parents
54
# set_path_id setting id when there are parents with the id in the parents
55
# set_path_id setting id when state is not in memory
56
# set_path_id setting id when state is in memory unmodified
57
# set_path_id setting id when state is in memory modified
59
class TestCaseWithDirState(TestCaseWithTransport):
60
"""Helper functions for creating DirState objects with various content."""
62
def create_empty_dirstate(self):
63
"""Return a locked but empty dirstate"""
64
state = dirstate.DirState.initialize('dirstate')
67
def create_dirstate_with_root(self):
68
"""Return a write-locked state with a single root entry."""
69
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
70
root_entry_direntry = ('', '', 'a-root-value'), [
71
('d', '', 0, False, packed_stat),
74
dirblocks.append(('', [root_entry_direntry]))
75
dirblocks.append(('', []))
76
state = self.create_empty_dirstate()
78
state._set_data([], dirblocks)
85
def create_dirstate_with_root_and_subdir(self):
86
"""Return a locked DirState with a root and a subdir"""
87
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
88
subdir_entry = ('', 'subdir', 'subdir-id'), [
89
('d', '', 0, False, packed_stat),
91
state = self.create_dirstate_with_root()
93
dirblocks = list(state._dirblocks)
94
dirblocks[1][1].append(subdir_entry)
95
state._set_data([], dirblocks)
101
def create_complex_dirstate(self):
102
"""This dirstate contains multiple files and directories.
112
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
114
# Notice that a/e is an empty directory.
116
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
117
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
118
root_entry = ('', '', 'a-root-value'), [
119
('d', '', 0, False, packed_stat),
121
a_entry = ('', 'a', 'a-dir'), [
122
('d', '', 0, False, packed_stat),
124
b_entry = ('', 'b', 'b-dir'), [
125
('d', '', 0, False, packed_stat),
127
c_entry = ('', 'c', 'c-file'), [
128
('f', null_sha, 10, False, packed_stat),
130
d_entry = ('', 'd', 'd-file'), [
131
('f', null_sha, 20, False, packed_stat),
133
e_entry = ('a', 'e', 'e-dir'), [
134
('d', '', 0, False, packed_stat),
136
f_entry = ('a', 'f', 'f-file'), [
137
('f', null_sha, 30, False, packed_stat),
139
g_entry = ('b', 'g', 'g-file'), [
140
('f', null_sha, 30, False, packed_stat),
142
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
143
('f', null_sha, 40, False, packed_stat),
146
dirblocks.append(('', [root_entry]))
147
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
148
dirblocks.append(('a', [e_entry, f_entry]))
149
dirblocks.append(('b', [g_entry, h_entry]))
150
state = dirstate.DirState.initialize('dirstate')
153
state._set_data([], dirblocks)
159
def check_state_with_reopen(self, expected_result, state):
160
"""Check that state has current state expected_result.
162
This will check the current state, open the file anew and check it
164
This function expects the current state to be locked for writing, and
165
will unlock it before re-opening.
166
This is required because we can't open a lock_read() while something
167
else has a lock_write().
168
write => mutually exclusive lock
171
# The state should already be write locked, since we just had to do
172
# some operation to get here.
173
assert state._lock_token is not None
175
self.assertEqual(expected_result[0], state.get_parent_ids())
176
# there should be no ghosts in this tree.
177
self.assertEqual([], state.get_ghosts())
178
# there should be one fileid in this tree - the root of the tree.
179
self.assertEqual(expected_result[1], list(state._iter_entries()))
183
del state # Callers should unlock
184
state = dirstate.DirState.on_file('dirstate')
187
self.assertEqual(expected_result[1], list(state._iter_entries()))
192
class TestTreeToDirState(TestCaseWithDirState):
194
def test_empty_to_dirstate(self):
195
"""We should be able to create a dirstate for an empty tree."""
196
# There are no files on disk and no parents
197
tree = self.make_branch_and_tree('tree')
198
expected_result = ([], [
199
(('', '', tree.path2id('')), # common details
200
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
202
state = dirstate.DirState.from_tree(tree, 'dirstate')
204
self.check_state_with_reopen(expected_result, state)
206
def test_1_parents_empty_to_dirstate(self):
207
# create a parent by doing a commit
208
tree = self.make_branch_and_tree('tree')
209
rev_id = tree.commit('first post').encode('utf8')
210
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
211
expected_result = ([rev_id], [
212
(('', '', tree.path2id('')), # common details
213
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
214
('d', '', 0, False, rev_id), # first parent details
216
state = dirstate.DirState.from_tree(tree, 'dirstate')
217
self.check_state_with_reopen(expected_result, state)
220
def test_2_parents_empty_to_dirstate(self):
221
# create a parent by doing a commit
222
tree = self.make_branch_and_tree('tree')
223
rev_id = tree.commit('first post')
224
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
225
rev_id2 = tree2.commit('second post', allow_pointless=True)
226
tree.merge_from_branch(tree2.branch)
227
expected_result = ([rev_id, rev_id2], [
228
(('', '', tree.path2id('')), # common details
229
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
230
('d', '', 0, False, rev_id), # first parent details
231
('d', '', 0, False, rev_id2), # second parent details
233
state = dirstate.DirState.from_tree(tree, 'dirstate')
234
self.check_state_with_reopen(expected_result, state)
237
def test_empty_unknowns_are_ignored_to_dirstate(self):
238
"""We should be able to create a dirstate for an empty tree."""
239
# There are no files on disk and no parents
240
tree = self.make_branch_and_tree('tree')
241
self.build_tree(['tree/unknown'])
242
expected_result = ([], [
243
(('', '', tree.path2id('')), # common details
244
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
246
state = dirstate.DirState.from_tree(tree, 'dirstate')
247
self.check_state_with_reopen(expected_result, state)
249
def get_tree_with_a_file(self):
250
tree = self.make_branch_and_tree('tree')
251
self.build_tree(['tree/a file'])
252
tree.add('a file', 'a file id')
255
def test_non_empty_no_parents_to_dirstate(self):
256
"""We should be able to create a dirstate for an empty tree."""
257
# There are files on disk and no parents
258
tree = self.get_tree_with_a_file()
259
expected_result = ([], [
260
(('', '', tree.path2id('')), # common details
261
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
263
(('', 'a file', 'a file id'), # common
264
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
267
state = dirstate.DirState.from_tree(tree, 'dirstate')
268
self.check_state_with_reopen(expected_result, state)
270
def test_1_parents_not_empty_to_dirstate(self):
271
# create a parent by doing a commit
272
tree = self.get_tree_with_a_file()
273
rev_id = tree.commit('first post').encode('utf8')
274
# change the current content to be different this will alter stat, sha
276
self.build_tree_contents([('tree/a file', 'new content\n')])
277
expected_result = ([rev_id], [
278
(('', '', tree.path2id('')), # common details
279
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
280
('d', '', 0, False, rev_id), # first parent details
282
(('', 'a file', 'a file id'), # common
283
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
284
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
285
rev_id), # first parent
288
state = dirstate.DirState.from_tree(tree, 'dirstate')
289
self.check_state_with_reopen(expected_result, state)
291
def test_2_parents_not_empty_to_dirstate(self):
292
# create a parent by doing a commit
293
tree = self.get_tree_with_a_file()
294
rev_id = tree.commit('first post').encode('utf8')
295
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
296
# change the current content to be different this will alter stat, sha
298
self.build_tree_contents([('tree2/a file', 'merge content\n')])
299
rev_id2 = tree2.commit('second post').encode('utf8')
300
tree.merge_from_branch(tree2.branch)
301
# change the current content to be different this will alter stat, sha
302
# and length again, giving us three distinct values:
303
self.build_tree_contents([('tree/a file', 'new content\n')])
304
expected_result = ([rev_id, rev_id2], [
305
(('', '', tree.path2id('')), # common details
306
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
307
('d', '', 0, False, rev_id), # first parent details
308
('d', '', 0, False, rev_id2), # second parent details
310
(('', 'a file', 'a file id'), # common
311
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
312
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
313
rev_id), # first parent
314
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
315
rev_id2), # second parent
318
state = dirstate.DirState.from_tree(tree, 'dirstate')
319
self.check_state_with_reopen(expected_result, state)
321
def test_colliding_fileids(self):
322
# test insertion of parents creating several entries at the same path.
323
# we used to have a bug where they could cause the dirstate to break
324
# its ordering invariants.
325
# create some trees to test from
328
tree = self.make_branch_and_tree('tree%d' % i)
329
self.build_tree(['tree%d/name' % i,])
330
tree.add(['name'], ['file-id%d' % i])
331
revision_id = 'revid-%d' % i
332
tree.commit('message', rev_id=revision_id)
333
parents.append((revision_id,
334
tree.branch.repository.revision_tree(revision_id)))
335
# now fold these trees into a dirstate
336
state = dirstate.DirState.initialize('dirstate')
338
state.set_parent_trees(parents, [])
344
class TestDirStateOnFile(TestCaseWithDirState):
346
def test_construct_with_path(self):
347
tree = self.make_branch_and_tree('tree')
348
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
349
# we want to be able to get the lines of the dirstate that we will
351
lines = state.get_lines()
353
self.build_tree_contents([('dirstate', ''.join(lines))])
355
# no parents, default tree content
356
expected_result = ([], [
357
(('', '', tree.path2id('')), # common details
358
# current tree details, but new from_tree skips statting, it
359
# uses set_state_from_inventory, and thus depends on the
361
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
364
state = dirstate.DirState.on_file('dirstate')
365
state.lock_write() # check_state_with_reopen will save() and unlock it
366
self.check_state_with_reopen(expected_result, state)
368
def test_can_save_clean_on_file(self):
369
tree = self.make_branch_and_tree('tree')
370
state = dirstate.DirState.from_tree(tree, 'dirstate')
372
# doing a save should work here as there have been no changes.
374
# TODO: stat it and check it hasn't changed; may require waiting
375
# for the state accuracy window.
380
class TestDirStateInitialize(TestCaseWithDirState):
382
def test_initialize(self):
383
expected_result = ([], [
384
(('', '', 'TREE_ROOT'), # common details
385
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
388
state = dirstate.DirState.initialize('dirstate')
390
self.assertIsInstance(state, dirstate.DirState)
391
lines = state.get_lines()
392
self.assertFileEqual(''.join(state.get_lines()),
394
self.check_state_with_reopen(expected_result, state)
400
class TestDirStateManipulations(TestCaseWithDirState):
402
def test_set_state_from_inventory_no_content_no_parents(self):
403
# setting the current inventory is a slow but important api to support.
404
tree1 = self.make_branch_and_memory_tree('tree1')
408
revid1 = tree1.commit('foo').encode('utf8')
409
root_id = tree1.inventory.root.file_id
410
inv = tree1.inventory
413
expected_result = [], [
414
(('', '', root_id), [
415
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
416
state = dirstate.DirState.initialize('dirstate')
418
state.set_state_from_inventory(inv)
419
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
421
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
422
state._dirblock_state)
427
# This will unlock it
428
self.check_state_with_reopen(expected_result, state)
430
def test_set_path_id_no_parents(self):
431
"""The id of a path can be changed trivally with no parents."""
432
state = dirstate.DirState.initialize('dirstate')
434
# check precondition to be sure the state does change appropriately.
436
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
437
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
438
list(state._iter_entries()))
439
state.set_path_id('', 'foobarbaz')
441
(('', '', 'foobarbaz'), [('d', '', 0, False,
442
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
443
self.assertEqual(expected_rows, list(state._iter_entries()))
444
# should work across save too
448
state = dirstate.DirState.on_file('dirstate')
452
self.assertEqual(expected_rows, list(state._iter_entries()))
456
def test_set_path_id_with_parents(self):
457
"""Set the root file id in a dirstate with parents"""
458
mt = self.make_branch_and_tree('mt')
459
# in case the default tree format uses a different root id
460
mt.set_root_id('TREE_ROOT')
461
mt.commit('foo', rev_id='parent-revid')
462
rt = mt.branch.repository.revision_tree('parent-revid')
463
state = dirstate.DirState.initialize('dirstate')
466
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
467
state.set_path_id('', 'foobarbaz')
469
# now see that it is what we expected
471
(('', '', 'TREE_ROOT'),
472
[('a', '', 0, False, ''),
473
('d', '', 0, False, 'parent-revid'),
475
(('', '', 'foobarbaz'),
476
[('d', '', 0, False, ''),
477
('a', '', 0, False, ''),
481
self.assertEqual(expected_rows, list(state._iter_entries()))
482
# should work across save too
486
# now flush & check we get the same
487
state = dirstate.DirState.on_file('dirstate')
491
self.assertEqual(expected_rows, list(state._iter_entries()))
494
# now change within an existing file-backed state
498
state.set_path_id('', 'tree-root-2')
504
def test_set_parent_trees_no_content(self):
505
# set_parent_trees is a slow but important api to support.
506
tree1 = self.make_branch_and_memory_tree('tree1')
510
revid1 = tree1.commit('foo')
513
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
514
tree2 = MemoryTree.create_on_branch(branch2)
517
revid2 = tree2.commit('foo')
518
root_id = tree2.inventory.root.file_id
521
state = dirstate.DirState.initialize('dirstate')
523
state.set_path_id('', root_id)
524
state.set_parent_trees(
525
((revid1, tree1.branch.repository.revision_tree(revid1)),
526
(revid2, tree2.branch.repository.revision_tree(revid2)),
527
('ghost-rev', None)),
529
# check we can reopen and use the dirstate after setting parent
536
state = dirstate.DirState.on_file('dirstate')
539
self.assertEqual([revid1, revid2, 'ghost-rev'],
540
state.get_parent_ids())
541
# iterating the entire state ensures that the state is parsable.
542
list(state._iter_entries())
543
# be sure that it sets not appends - change it
544
state.set_parent_trees(
545
((revid1, tree1.branch.repository.revision_tree(revid1)),
546
('ghost-rev', None)),
548
# and now put it back.
549
state.set_parent_trees(
550
((revid1, tree1.branch.repository.revision_tree(revid1)),
551
(revid2, tree2.branch.repository.revision_tree(revid2)),
552
('ghost-rev', tree2.branch.repository.revision_tree(None))),
554
self.assertEqual([revid1, revid2, 'ghost-rev'],
555
state.get_parent_ids())
556
# the ghost should be recorded as such by set_parent_trees.
557
self.assertEqual(['ghost-rev'], state.get_ghosts())
559
[(('', '', root_id), [
560
('d', '', 0, False, dirstate.DirState.NULLSTAT),
561
('d', '', 0, False, revid1),
562
('d', '', 0, False, revid2)
564
list(state._iter_entries()))
568
def test_set_parent_trees_file_missing_from_tree(self):
569
# Adding a parent tree may reference files not in the current state.
570
# they should get listed just once by id, even if they are in two
572
# set_parent_trees is a slow but important api to support.
573
tree1 = self.make_branch_and_memory_tree('tree1')
577
tree1.add(['a file'], ['file-id'], ['file'])
578
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
579
revid1 = tree1.commit('foo')
582
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
583
tree2 = MemoryTree.create_on_branch(branch2)
586
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
587
revid2 = tree2.commit('foo')
588
root_id = tree2.inventory.root.file_id
591
# check the layout in memory
592
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
593
(('', '', root_id), [
594
('d', '', 0, False, dirstate.DirState.NULLSTAT),
595
('d', '', 0, False, revid1.encode('utf8')),
596
('d', '', 0, False, revid2.encode('utf8'))
598
(('', 'a file', 'file-id'), [
599
('a', '', 0, False, ''),
600
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
601
revid1.encode('utf8')),
602
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
603
revid2.encode('utf8'))
606
state = dirstate.DirState.initialize('dirstate')
608
state.set_path_id('', root_id)
609
state.set_parent_trees(
610
((revid1, tree1.branch.repository.revision_tree(revid1)),
611
(revid2, tree2.branch.repository.revision_tree(revid2)),
617
# check_state_with_reopen will unlock
618
self.check_state_with_reopen(expected_result, state)
620
### add a path via _set_data - so we dont need delta work, just
621
# raw data in, and ensure that it comes out via get_lines happily.
623
def test_add_path_to_root_no_parents_all_data(self):
624
# The most trivial addition of a path is when there are no parents and
625
# its in the root and all data about the file is supplied
626
self.build_tree(['a file'])
627
stat = os.lstat('a file')
628
# the 1*20 is the sha1 pretend value.
629
state = dirstate.DirState.initialize('dirstate')
631
(('', '', 'TREE_ROOT'), [
632
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
634
(('', 'a file', 'a file id'), [
635
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
639
state.add('a file', 'a file id', 'file', stat, '1'*20)
640
# having added it, it should be in the output of iter_entries.
641
self.assertEqual(expected_entries, list(state._iter_entries()))
642
# saving and reloading should not affect this.
646
state = dirstate.DirState.on_file('dirstate')
649
self.assertEqual(expected_entries, list(state._iter_entries()))
653
def test_add_path_to_unversioned_directory(self):
654
"""Adding a path to an unversioned directory should error.
656
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
657
once dirstate is stable and if it is merged with WorkingTree3, consider
658
removing this copy of the test.
660
self.build_tree(['unversioned/', 'unversioned/a file'])
661
state = dirstate.DirState.initialize('dirstate')
663
self.assertRaises(errors.NotVersionedError, state.add,
664
'unversioned/a file', 'a file id', 'file', None, None)
668
def test_add_directory_to_root_no_parents_all_data(self):
669
# The most trivial addition of a dir is when there are no parents and
670
# its in the root and all data about the file is supplied
671
self.build_tree(['a dir/'])
672
stat = os.lstat('a dir')
674
(('', '', 'TREE_ROOT'), [
675
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
677
(('', 'a dir', 'a dir id'), [
678
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
681
state = dirstate.DirState.initialize('dirstate')
683
state.add('a dir', 'a dir id', 'directory', stat, None)
684
# having added it, it should be in the output of iter_entries.
685
self.assertEqual(expected_entries, list(state._iter_entries()))
686
# saving and reloading should not affect this.
690
state = dirstate.DirState.on_file('dirstate')
694
self.assertEqual(expected_entries, list(state._iter_entries()))
698
def test_add_symlink_to_root_no_parents_all_data(self):
699
# The most trivial addition of a symlink when there are no parents and
700
# its in the root and all data about the file is supplied
701
## TODO: windows: dont fail this test. Also, how are symlinks meant to
702
# be represented on windows.
703
os.symlink('target', 'a link')
704
stat = os.lstat('a link')
706
(('', '', 'TREE_ROOT'), [
707
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
709
(('', 'a link', 'a link id'), [
710
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
713
state = dirstate.DirState.initialize('dirstate')
715
state.add('a link', 'a link id', 'symlink', stat, 'target')
716
# having added it, it should be in the output of iter_entries.
717
self.assertEqual(expected_entries, list(state._iter_entries()))
718
# saving and reloading should not affect this.
722
state = dirstate.DirState.on_file('dirstate')
725
self.assertEqual(expected_entries, list(state._iter_entries()))
729
def test_add_directory_and_child_no_parents_all_data(self):
730
# after adding a directory, we should be able to add children to it.
731
self.build_tree(['a dir/', 'a dir/a file'])
732
dirstat = os.lstat('a dir')
733
filestat = os.lstat('a dir/a file')
735
(('', '', 'TREE_ROOT'), [
736
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
738
(('', 'a dir', 'a dir id'), [
739
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
741
(('a dir', 'a file', 'a file id'), [
742
('f', '1'*20, 25, False,
743
dirstate.pack_stat(filestat)), # current tree details
746
state = dirstate.DirState.initialize('dirstate')
748
state.add('a dir', 'a dir id', 'directory', dirstat, None)
749
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
750
# added it, it should be in the output of iter_entries.
751
self.assertEqual(expected_entries, list(state._iter_entries()))
752
# saving and reloading should not affect this.
756
state = dirstate.DirState.on_file('dirstate')
759
self.assertEqual(expected_entries, list(state._iter_entries()))
763
def test_add_tree_reference(self):
764
# make a dirstate and add a tree reference
765
state = dirstate.DirState.initialize('dirstate')
767
('', 'subdir', 'subdir-id'),
768
[('t', 'subtree-123123', 0, False,
769
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
772
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
773
entry = state._get_entry(0, 'subdir-id', 'subdir')
774
self.assertEqual(entry, expected_entry)
779
# now check we can read it back
783
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
784
self.assertEqual(entry, entry2)
785
self.assertEqual(entry, expected_entry)
786
# and lookup by id should work too
787
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
788
self.assertEqual(entry, expected_entry)
792
def test_add_forbidden_names(self):
793
state = dirstate.DirState.initialize('dirstate')
794
self.assertRaises(errors.BzrError,
795
state.add, '.', 'ass-id', 'directory', None, None)
796
self.assertRaises(errors.BzrError,
797
state.add, '..', 'ass-id', 'directory', None, None)
800
class TestGetLines(TestCaseWithDirState):
802
def test_get_line_with_2_rows(self):
803
state = self.create_dirstate_with_root_and_subdir()
805
self.assertEqual(['#bazaar dirstate flat format 3\n',
806
'adler32: -1327947603\n',
810
'\x00\x00a-root-value\x00'
811
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
812
'\x00subdir\x00subdir-id\x00'
813
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
814
], state.get_lines())
818
def test_entry_to_line(self):
819
state = self.create_dirstate_with_root()
822
'\x00\x00a-root-value\x00d\x00\x000\x00n'
823
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
824
state._entry_to_line(state._dirblocks[0][1][0]))
828
def test_entry_to_line_with_parent(self):
829
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
830
root_entry = ('', '', 'a-root-value'), [
831
('d', '', 0, False, packed_stat), # current tree details
832
# first: a pointer to the current location
833
('a', 'dirname/basename', 0, False, ''),
835
state = dirstate.DirState.initialize('dirstate')
838
'\x00\x00a-root-value\x00'
839
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
840
'a\x00dirname/basename\x000\x00n\x00',
841
state._entry_to_line(root_entry))
845
def test_entry_to_line_with_two_parents_at_different_paths(self):
846
# / in the tree, at / in one parent and /dirname/basename in the other.
847
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
848
root_entry = ('', '', 'a-root-value'), [
849
('d', '', 0, False, packed_stat), # current tree details
850
('d', '', 0, False, 'rev_id'), # first parent details
851
# second: a pointer to the current location
852
('a', 'dirname/basename', 0, False, ''),
854
state = dirstate.DirState.initialize('dirstate')
857
'\x00\x00a-root-value\x00'
858
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
859
'd\x00\x000\x00n\x00rev_id\x00'
860
'a\x00dirname/basename\x000\x00n\x00',
861
state._entry_to_line(root_entry))
865
def test_iter_entries(self):
866
# we should be able to iterate the dirstate entries from end to end
867
# this is for get_lines to be easy to read.
868
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
870
root_entries = [(('', '', 'a-root-value'), [
871
('d', '', 0, False, packed_stat), # current tree details
873
dirblocks.append(('', root_entries))
874
# add two files in the root
875
subdir_entry = ('', 'subdir', 'subdir-id'), [
876
('d', '', 0, False, packed_stat), # current tree details
878
afile_entry = ('', 'afile', 'afile-id'), [
879
('f', 'sha1value', 34, False, packed_stat), # current tree details
881
dirblocks.append(('', [subdir_entry, afile_entry]))
883
file_entry2 = ('subdir', '2file', '2file-id'), [
884
('f', 'sha1value', 23, False, packed_stat), # current tree details
886
dirblocks.append(('subdir', [file_entry2]))
887
state = dirstate.DirState.initialize('dirstate')
889
state._set_data([], dirblocks)
890
expected_entries = [root_entries[0], subdir_entry, afile_entry,
892
self.assertEqual(expected_entries, list(state._iter_entries()))
897
class TestGetBlockRowIndex(TestCaseWithDirState):
899
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
900
file_present, state, dirname, basename, tree_index):
901
self.assertEqual((block_index, row_index, dir_present, file_present),
902
state._get_block_entry_index(dirname, basename, tree_index))
904
block = state._dirblocks[block_index]
905
self.assertEqual(dirname, block[0])
906
if dir_present and file_present:
907
row = state._dirblocks[block_index][1][row_index]
908
self.assertEqual(dirname, row[0][0])
909
self.assertEqual(basename, row[0][1])
911
def test_simple_structure(self):
912
state = self.create_dirstate_with_root_and_subdir()
913
self.addCleanup(state.unlock)
914
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
915
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
916
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
917
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
918
self.assertBlockRowIndexEqual(2, 0, False, False, state,
921
def test_complex_structure_exists(self):
922
state = self.create_complex_dirstate()
923
self.addCleanup(state.unlock)
924
# Make sure we can find everything that exists
925
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
926
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
927
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
928
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
929
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
930
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
931
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
932
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
933
self.assertBlockRowIndexEqual(3, 1, True, True, state,
936
def test_complex_structure_missing(self):
937
state = self.create_complex_dirstate()
938
self.addCleanup(state.unlock)
939
# Make sure things would be inserted in the right locations
940
# '_' comes before 'a'
941
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
942
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
943
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
944
self.assertBlockRowIndexEqual(1, 4, True, False, state,
946
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
947
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
948
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
949
# This would be inserted between a/ and b/
950
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
952
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
955
class TestGetEntry(TestCaseWithDirState):
957
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
958
"""Check that the right entry is returned for a request to getEntry."""
959
entry = state._get_entry(index, path_utf8=path)
961
self.assertEqual((None, None), entry)
964
self.assertEqual((dirname, basename, file_id), cur[:3])
966
def test_simple_structure(self):
967
state = self.create_dirstate_with_root_and_subdir()
968
self.addCleanup(state.unlock)
969
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
970
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
971
self.assertEntryEqual(None, None, None, state, 'missing', 0)
972
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
973
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
975
def test_complex_structure_exists(self):
976
state = self.create_complex_dirstate()
977
self.addCleanup(state.unlock)
978
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
979
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
980
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
981
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
982
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
983
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
984
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
985
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
986
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
989
def test_complex_structure_missing(self):
990
state = self.create_complex_dirstate()
991
self.addCleanup(state.unlock)
992
self.assertEntryEqual(None, None, None, state, '_', 0)
993
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
994
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
995
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
997
def test_get_entry_uninitialized(self):
998
"""Calling get_entry will load data if it needs to"""
999
state = self.create_dirstate_with_root()
1005
state = dirstate.DirState.on_file('dirstate')
1008
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1009
state._header_state)
1010
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1011
state._dirblock_state)
1012
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1017
class TestDirstateSortOrder(TestCaseWithTransport):
1018
"""Test that DirState adds entries in the right order."""
1020
def test_add_sorting(self):
1021
"""Add entries in lexicographical order, we get path sorted order.
1023
This tests it to a depth of 4, to make sure we don't just get it right
1024
at a single depth. 'a/a' should come before 'a-a', even though it
1025
doesn't lexicographically.
1027
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1028
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1031
state = dirstate.DirState.initialize('dirstate')
1032
self.addCleanup(state.unlock)
1034
fake_stat = os.stat('dirstate')
1036
d_id = d.replace('/', '_')+'-id'
1037
file_path = d + '/f'
1038
file_id = file_path.replace('/', '_')+'-id'
1039
state.add(d, d_id, 'directory', fake_stat, null_sha)
1040
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1042
expected = ['', '', 'a',
1043
'a/a', 'a/a/a', 'a/a/a/a',
1044
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1046
split = lambda p:p.split('/')
1047
self.assertEqual(sorted(expected, key=split), expected)
1048
dirblock_names = [d[0] for d in state._dirblocks]
1049
self.assertEqual(expected, dirblock_names)
1051
def test_set_parent_trees_correct_order(self):
1052
"""After calling set_parent_trees() we should maintain the order."""
1053
dirs = ['a', 'a-a', 'a/a']
1055
state = dirstate.DirState.initialize('dirstate')
1056
self.addCleanup(state.unlock)
1058
fake_stat = os.stat('dirstate')
1060
d_id = d.replace('/', '_')+'-id'
1061
file_path = d + '/f'
1062
file_id = file_path.replace('/', '_')+'-id'
1063
state.add(d, d_id, 'directory', fake_stat, null_sha)
1064
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1066
expected = ['', '', 'a', 'a/a', 'a-a']
1067
dirblock_names = [d[0] for d in state._dirblocks]
1068
self.assertEqual(expected, dirblock_names)
1070
# *really* cheesy way to just get an empty tree
1071
repo = self.make_repository('repo')
1072
empty_tree = repo.revision_tree(None)
1073
state.set_parent_trees([('null:', empty_tree)], [])
1075
dirblock_names = [d[0] for d in state._dirblocks]
1076
self.assertEqual(expected, dirblock_names)
1079
class InstrumentedDirState(dirstate.DirState):
1080
"""An DirState with instrumented sha1 functionality."""
1082
def __init__(self, path):
1083
super(InstrumentedDirState, self).__init__(path)
1084
self._time_offset = 0
1087
def _sha_cutoff_time(self):
1088
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1089
self._cutoff_time = timestamp + self._time_offset
1091
def _sha1_file(self, abspath, entry):
1092
self._log.append(('sha1', abspath))
1093
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1095
def _read_link(self, abspath, old_link):
1096
self._log.append(('read_link', abspath, old_link))
1097
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1099
def _lstat(self, abspath, entry):
1100
self._log.append(('lstat', abspath))
1101
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1103
def _is_executable(self, mode, old_executable):
1104
self._log.append(('is_exec', mode, old_executable))
1105
return super(InstrumentedDirState, self)._is_executable(mode,
1108
def adjust_time(self, secs):
1109
"""Move the clock forward or back.
1111
:param secs: The amount to adjust the clock by. Positive values make it
1112
seem as if we are in the future, negative values make it seem like we
1115
self._time_offset += secs
1116
self._cutoff_time = None
1119
class _FakeStat(object):
1120
"""A class with the same attributes as a real stat result."""
1122
def __init__(self, size, mtime, ctime, dev, ino, mode):
1124
self.st_mtime = mtime
1125
self.st_ctime = ctime
1131
class TestUpdateEntry(TestCaseWithDirState):
1132
"""Test the DirState.update_entry functions"""
1134
def get_state_with_a(self):
1135
"""Create a DirState tracking a single object named 'a'"""
1136
state = InstrumentedDirState.initialize('dirstate')
1137
self.addCleanup(state.unlock)
1138
state.add('a', 'a-id', 'file', None, '')
1139
entry = state._get_entry(0, path_utf8='a')
1142
def test_update_entry(self):
1143
state, entry = self.get_state_with_a()
1144
self.build_tree(['a'])
1145
# Add one where we don't provide the stat or sha already
1146
self.assertEqual(('', 'a', 'a-id'), entry[0])
1147
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1149
# Flush the buffers to disk
1151
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1152
state._dirblock_state)
1154
stat_value = os.lstat('a')
1155
packed_stat = dirstate.pack_stat(stat_value)
1156
link_or_sha1 = state.update_entry(entry, abspath='a',
1157
stat_value=stat_value)
1158
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1161
# The dirblock entry should be updated with the new info
1162
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1164
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1165
state._dirblock_state)
1166
mode = stat_value.st_mode
1167
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1170
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1171
state._dirblock_state)
1173
# If we do it again right away, we don't know if the file has changed
1174
# so we will re-read the file. Roll the clock back so the file is
1175
# guaranteed to look too new.
1176
state.adjust_time(-10)
1178
link_or_sha1 = state.update_entry(entry, abspath='a',
1179
stat_value=stat_value)
1180
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1181
('sha1', 'a'), ('is_exec', mode, False),
1183
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1185
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1186
state._dirblock_state)
1189
# However, if we move the clock forward so the file is considered
1190
# "stable", it should just returned the cached value.
1191
state.adjust_time(20)
1192
link_or_sha1 = state.update_entry(entry, abspath='a',
1193
stat_value=stat_value)
1194
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1196
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1197
('sha1', 'a'), ('is_exec', mode, False),
1200
def test_update_entry_no_stat_value(self):
1201
"""Passing the stat_value is optional."""
1202
state, entry = self.get_state_with_a()
1203
state.adjust_time(-10) # Make sure the file looks new
1204
self.build_tree(['a'])
1205
# Add one where we don't provide the stat or sha already
1206
link_or_sha1 = state.update_entry(entry, abspath='a')
1207
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1209
stat_value = os.lstat('a')
1210
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1211
('is_exec', stat_value.st_mode, False),
1214
def test_update_entry_symlink(self):
1215
"""Update entry should read symlinks."""
1216
if not osutils.has_symlinks():
1217
return # PlatformDeficiency / TestSkipped
1218
state, entry = self.get_state_with_a()
1220
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1221
state._dirblock_state)
1222
os.symlink('target', 'a')
1224
state.adjust_time(-10) # Make the symlink look new
1225
stat_value = os.lstat('a')
1226
packed_stat = dirstate.pack_stat(stat_value)
1227
link_or_sha1 = state.update_entry(entry, abspath='a',
1228
stat_value=stat_value)
1229
self.assertEqual('target', link_or_sha1)
1230
self.assertEqual([('read_link', 'a', '')], state._log)
1231
# Dirblock is updated
1232
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1234
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1235
state._dirblock_state)
1237
# Because the stat_value looks new, we should re-read the target
1238
link_or_sha1 = state.update_entry(entry, abspath='a',
1239
stat_value=stat_value)
1240
self.assertEqual('target', link_or_sha1)
1241
self.assertEqual([('read_link', 'a', ''),
1242
('read_link', 'a', 'target'),
1244
state.adjust_time(+20) # Skip into the future, all files look old
1245
link_or_sha1 = state.update_entry(entry, abspath='a',
1246
stat_value=stat_value)
1247
self.assertEqual('target', link_or_sha1)
1248
# There should not be a new read_link call.
1249
# (this is a weak assertion, because read_link is fairly inexpensive,
1250
# versus the number of symlinks that we would have)
1251
self.assertEqual([('read_link', 'a', ''),
1252
('read_link', 'a', 'target'),
1255
def test_update_entry_dir(self):
1256
state, entry = self.get_state_with_a()
1257
self.build_tree(['a/'])
1258
self.assertIs(None, state.update_entry(entry, 'a'))
1260
def create_and_test_file(self, state, entry):
1261
"""Create a file at 'a' and verify the state finds it.
1263
The state should already be versioning *something* at 'a'. This makes
1264
sure that state.update_entry recognizes it as a file.
1266
self.build_tree(['a'])
1267
stat_value = os.lstat('a')
1268
packed_stat = dirstate.pack_stat(stat_value)
1270
link_or_sha1 = state.update_entry(entry, abspath='a')
1271
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1273
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1277
def create_and_test_dir(self, state, entry):
1278
"""Create a directory at 'a' and verify the state finds it.
1280
The state should already be versioning *something* at 'a'. This makes
1281
sure that state.update_entry recognizes it as a directory.
1283
self.build_tree(['a/'])
1284
stat_value = os.lstat('a')
1285
packed_stat = dirstate.pack_stat(stat_value)
1287
link_or_sha1 = state.update_entry(entry, abspath='a')
1288
self.assertIs(None, link_or_sha1)
1289
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1293
def create_and_test_symlink(self, state, entry):
1294
"""Create a symlink at 'a' and verify the state finds it.
1296
The state should already be versioning *something* at 'a'. This makes
1297
sure that state.update_entry recognizes it as a symlink.
1299
This should not be called if this platform does not have symlink
1302
os.symlink('path/to/foo', 'a')
1304
stat_value = os.lstat('a')
1305
packed_stat = dirstate.pack_stat(stat_value)
1307
link_or_sha1 = state.update_entry(entry, abspath='a')
1308
self.assertEqual('path/to/foo', link_or_sha1)
1309
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1313
def test_update_missing_file(self):
1314
state, entry = self.get_state_with_a()
1315
packed_stat = self.create_and_test_file(state, entry)
1316
# Now if we delete the file, update_entry should recover and
1319
self.assertIs(None, state.update_entry(entry, abspath='a'))
1320
# And the record shouldn't be changed.
1321
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1322
self.assertEqual([('f', digest, 14, False, packed_stat)],
1325
def test_update_missing_dir(self):
1326
state, entry = self.get_state_with_a()
1327
packed_stat = self.create_and_test_dir(state, entry)
1328
# Now if we delete the directory, update_entry should recover and
1331
self.assertIs(None, state.update_entry(entry, abspath='a'))
1332
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1334
def test_update_missing_symlink(self):
1335
if not osutils.has_symlinks():
1336
return # PlatformDeficiency / TestSkipped
1337
state, entry = self.get_state_with_a()
1338
packed_stat = self.create_and_test_symlink(state, entry)
1340
self.assertIs(None, state.update_entry(entry, abspath='a'))
1341
# And the record shouldn't be changed.
1342
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1345
def test_update_file_to_dir(self):
1346
"""If a file changes to a directory we return None for the sha.
1347
We also update the inventory record.
1349
state, entry = self.get_state_with_a()
1350
self.create_and_test_file(state, entry)
1352
self.create_and_test_dir(state, entry)
1354
def test_update_file_to_symlink(self):
1355
"""File becomes a symlink"""
1356
if not osutils.has_symlinks():
1357
return # PlatformDeficiency / TestSkipped
1358
state, entry = self.get_state_with_a()
1359
self.create_and_test_file(state, entry)
1361
self.create_and_test_symlink(state, entry)
1363
def test_update_dir_to_file(self):
1364
"""Directory becoming a file updates the entry."""
1365
state, entry = self.get_state_with_a()
1366
self.create_and_test_dir(state, entry)
1368
self.create_and_test_file(state, entry)
1370
def test_update_dir_to_symlink(self):
1371
"""Directory becomes a symlink"""
1372
if not osutils.has_symlinks():
1373
return # PlatformDeficiency / TestSkipped
1374
state, entry = self.get_state_with_a()
1375
self.create_and_test_dir(state, entry)
1377
self.create_and_test_symlink(state, entry)
1379
def test_update_symlink_to_file(self):
1380
"""Symlink becomes a file"""
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
state, entry = self.get_state_with_a()
1389
self.create_and_test_symlink(state, entry)
1391
self.create_and_test_dir(state, entry)
1393
def test__is_executable_win32(self):
1394
state, entry = self.get_state_with_a()
1395
self.build_tree(['a'])
1397
# Make sure we are using the win32 implementation of _is_executable
1398
state._is_executable = state._is_executable_win32
1400
# The file on disk is not executable, but we are marking it as though
1401
# it is. With _is_executable_win32 we ignore what is on disk.
1402
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1404
stat_value = os.lstat('a')
1405
packed_stat = dirstate.pack_stat(stat_value)
1407
state.adjust_time(-10) # Make sure everything is new
1408
# Make sure it wants to kkkkkkkk
1409
state.update_entry(entry, abspath='a', stat_value=stat_value)
1411
# The row is updated, but the executable bit stays set.
1412
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1413
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1416
class TestPackStat(TestCaseWithTransport):
1418
def assertPackStat(self, expected, stat_value):
1419
"""Check the packed and serialized form of a stat value."""
1420
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1422
def test_pack_stat_int(self):
1423
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1424
# Make sure that all parameters have an impact on the packed stat.
1425
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1428
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1429
st.st_mtime = 1172758620
1431
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1432
st.st_ctime = 1172758630
1434
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1437
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1438
st.st_ino = 6499540L
1440
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1441
st.st_mode = 0100744
1443
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1445
def test_pack_stat_float(self):
1446
"""On some platforms mtime and ctime are floats.
1448
Make sure we don't get warnings or errors, and that we ignore changes <
1451
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1452
777L, 6499538L, 0100644)
1453
# These should all be the same as the integer counterparts
1454
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1455
st.st_mtime = 1172758620.0
1457
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1458
st.st_ctime = 1172758630.0
1460
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1461
# fractional seconds are discarded, so no change from above
1462
st.st_mtime = 1172758620.453
1463
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1464
st.st_ctime = 1172758630.228
1465
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1468
class TestBisect(TestCaseWithTransport):
1469
"""Test the ability to bisect into the disk format."""
1471
def create_basic_dirstate(self):
1472
"""Create a dirstate with a few files and directories.
1481
tree = self.make_branch_and_tree('tree')
1482
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1483
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1484
self.build_tree(['tree/' + p for p in paths])
1485
tree.set_root_id('TREE_ROOT')
1486
tree.add([p.rstrip('/') for p in paths], file_ids)
1487
tree.commit('initial', rev_id='rev-1')
1488
revision_id = 'rev-1'
1489
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1490
t = self.get_transport().clone('tree')
1491
a_text = t.get_bytes('a')
1492
a_sha = osutils.sha_string(a_text)
1494
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1495
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1496
c_text = t.get_bytes('b/c')
1497
c_sha = osutils.sha_string(c_text)
1499
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1500
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1501
e_text = t.get_bytes('b/d/e')
1502
e_sha = osutils.sha_string(e_text)
1504
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1505
f_text = t.get_bytes('f')
1506
f_sha = osutils.sha_string(f_text)
1508
null_stat = dirstate.DirState.NULLSTAT
1510
'':(('', '', 'TREE_ROOT'), [
1511
('d', '', 0, False, null_stat),
1512
('d', '', 0, False, revision_id),
1514
'a':(('', 'a', 'a-id'), [
1515
('f', '', 0, False, null_stat),
1516
('f', a_sha, a_len, False, revision_id),
1518
'b':(('', 'b', 'b-id'), [
1519
('d', '', 0, False, null_stat),
1520
('d', '', 0, False, revision_id),
1522
'b/c':(('b', 'c', 'c-id'), [
1523
('f', '', 0, False, null_stat),
1524
('f', c_sha, c_len, False, revision_id),
1526
'b/d':(('b', 'd', 'd-id'), [
1527
('d', '', 0, False, null_stat),
1528
('d', '', 0, False, revision_id),
1530
'b/d/e':(('b/d', 'e', 'e-id'), [
1531
('f', '', 0, False, null_stat),
1532
('f', e_sha, e_len, False, revision_id),
1534
'f':(('', 'f', 'f-id'), [
1535
('f', '', 0, False, null_stat),
1536
('f', f_sha, f_len, False, revision_id),
1539
state = dirstate.DirState.from_tree(tree, 'dirstate')
1544
# Use a different object, to make sure nothing is pre-cached in memory.
1545
state = dirstate.DirState.on_file('dirstate')
1547
self.addCleanup(state.unlock)
1548
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1549
state._dirblock_state)
1550
# This is code is only really tested if we actually have to make more
1551
# than one read, so set the page size to something smaller.
1552
# We want it to contain about 2.2 records, so that we have a couple
1553
# records that we can read per attempt
1554
state._bisect_page_size = 200
1555
return tree, state, expected
1557
def create_duplicated_dirstate(self):
1558
"""Create a dirstate with a deleted and added entries.
1560
This grabs a basic_dirstate, and then removes and re adds every entry
1563
tree, state, expected = self.create_basic_dirstate()
1564
# Now we will just remove and add every file so we get an extra entry
1565
# per entry. Unversion in reverse order so we handle subdirs
1566
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1567
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1568
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1570
# Update the expected dictionary.
1571
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1572
orig = expected[path]
1574
# This record was deleted in the current tree
1575
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1577
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1578
# And didn't exist in the basis tree
1579
expected[path2] = (new_key, [orig[1][0],
1580
dirstate.DirState.NULL_PARENT_DETAILS])
1582
# We will replace the 'dirstate' file underneath 'state', but that is
1583
# okay as lock as we unlock 'state' first.
1586
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1592
# But we need to leave state in a read-lock because we already have
1593
# a cleanup scheduled
1595
return tree, state, expected
1597
def create_renamed_dirstate(self):
1598
"""Create a dirstate with a few internal renames.
1600
This takes the basic dirstate, and moves the paths around.
1602
tree, state, expected = self.create_basic_dirstate()
1604
tree.rename_one('a', 'b/g')
1606
tree.rename_one('b/d', 'h')
1608
old_a = expected['a']
1609
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1610
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1611
('r', 'a', 0, False, '')])
1612
old_d = expected['b/d']
1613
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1614
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1615
('r', 'b/d', 0, False, '')])
1617
old_e = expected['b/d/e']
1618
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1620
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1621
('r', 'b/d/e', 0, False, '')])
1625
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1632
return tree, state, expected
1634
def assertBisect(self, expected_map, map_keys, state, paths):
1635
"""Assert that bisecting for paths returns the right result.
1637
:param expected_map: A map from key => entry value
1638
:param map_keys: The keys to expect for each path
1639
:param state: The DirState object.
1640
:param paths: A list of paths, these will automatically be split into
1641
(dir, name) tuples, and sorted according to how _bisect
1644
dir_names = sorted(osutils.split(p) for p in paths)
1645
result = state._bisect(dir_names)
1646
# For now, results are just returned in whatever order we read them.
1647
# We could sort by (dir, name, file_id) or something like that, but in
1648
# the end it would still be fairly arbitrary, and we don't want the
1649
# extra overhead if we can avoid it. So sort everything to make sure
1651
assert len(map_keys) == len(dir_names)
1653
for dir_name, keys in zip(dir_names, map_keys):
1655
# This should not be present in the output
1657
expected[dir_name] = sorted(expected_map[k] for k in keys)
1659
for dir_name in result:
1660
result[dir_name].sort()
1662
self.assertEqual(expected, result)
1664
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1665
"""Assert that bisecting for dirbblocks returns the right result.
1667
:param expected_map: A map from key => expected values
1668
:param map_keys: A nested list of paths we expect to be returned.
1669
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1670
:param state: The DirState object.
1671
:param paths: A list of directories
1673
result = state._bisect_dirblocks(paths)
1674
assert len(map_keys) == len(paths)
1677
for path, keys in zip(paths, map_keys):
1679
# This should not be present in the output
1681
expected[path] = sorted(expected_map[k] for k in keys)
1685
self.assertEqual(expected, result)
1687
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1688
"""Assert the return value of a recursive bisection.
1690
:param expected_map: A map from key => entry value
1691
:param map_keys: A list of paths we expect to be returned.
1692
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1693
:param state: The DirState object.
1694
:param paths: A list of files and directories. It will be broken up
1695
into (dir, name) pairs and sorted before calling _bisect_recursive.
1698
for key in map_keys:
1699
entry = expected_map[key]
1700
dir_name_id, trees_info = entry
1701
expected[dir_name_id] = trees_info
1703
dir_names = sorted(osutils.split(p) for p in paths)
1704
result = state._bisect_recursive(dir_names)
1706
self.assertEqual(expected, result)
1708
def test_bisect_each(self):
1709
"""Find a single record using bisect."""
1710
tree, state, expected = self.create_basic_dirstate()
1712
# Bisect should return the rows for the specified files.
1713
self.assertBisect(expected, [['']], state, [''])
1714
self.assertBisect(expected, [['a']], state, ['a'])
1715
self.assertBisect(expected, [['b']], state, ['b'])
1716
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1717
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1718
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1719
self.assertBisect(expected, [['f']], state, ['f'])
1721
def test_bisect_multi(self):
1722
"""Bisect can be used to find multiple records at the same time."""
1723
tree, state, expected = self.create_basic_dirstate()
1724
# Bisect should be capable of finding multiple entries at the same time
1725
self.assertBisect(expected, [['a'], ['b'], ['f']],
1726
state, ['a', 'b', 'f'])
1727
# ('', 'f') sorts before the others
1728
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1729
state, ['b/d', 'b/d/e', 'f'])
1731
def test_bisect_one_page(self):
1732
"""Test bisect when there is only 1 page to read"""
1733
tree, state, expected = self.create_basic_dirstate()
1734
state._bisect_page_size = 5000
1735
self.assertBisect(expected,[['']], state, [''])
1736
self.assertBisect(expected,[['a']], state, ['a'])
1737
self.assertBisect(expected,[['b']], state, ['b'])
1738
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1739
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1740
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1741
self.assertBisect(expected,[['f']], state, ['f'])
1742
self.assertBisect(expected,[['a'], ['b'], ['f']],
1743
state, ['a', 'b', 'f'])
1744
# ('', 'f') sorts before the others
1745
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1746
state, ['b/d', 'b/d/e', 'f'])
1748
def test_bisect_duplicate_paths(self):
1749
"""When bisecting for a path, handle multiple entries."""
1750
tree, state, expected = self.create_duplicated_dirstate()
1752
# Now make sure that both records are properly returned.
1753
self.assertBisect(expected, [['']], state, [''])
1754
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1755
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1756
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1757
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1758
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1760
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1762
def test_bisect_page_size_too_small(self):
1763
"""If the page size is too small, we will auto increase it."""
1764
tree, state, expected = self.create_basic_dirstate()
1765
state._bisect_page_size = 50
1766
self.assertBisect(expected, [None], state, ['b/e'])
1767
self.assertBisect(expected, [['a']], state, ['a'])
1768
self.assertBisect(expected, [['b']], state, ['b'])
1769
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1770
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1771
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1772
self.assertBisect(expected, [['f']], state, ['f'])
1774
def test_bisect_missing(self):
1775
"""Test that bisect return None if it cannot find a path."""
1776
tree, state, expected = self.create_basic_dirstate()
1777
self.assertBisect(expected, [None], state, ['foo'])
1778
self.assertBisect(expected, [None], state, ['b/foo'])
1779
self.assertBisect(expected, [None], state, ['bar/foo'])
1781
self.assertBisect(expected, [['a'], None, ['b/d']],
1782
state, ['a', 'foo', 'b/d'])
1784
def test_bisect_rename(self):
1785
"""Check that we find a renamed row."""
1786
tree, state, expected = self.create_renamed_dirstate()
1788
# Search for the pre and post renamed entries
1789
self.assertBisect(expected, [['a']], state, ['a'])
1790
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1791
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1792
self.assertBisect(expected, [['h']], state, ['h'])
1794
# What about b/d/e? shouldn't that also get 2 directory entries?
1795
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1796
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1798
def test_bisect_dirblocks(self):
1799
tree, state, expected = self.create_duplicated_dirstate()
1800
self.assertBisectDirBlocks(expected,
1801
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1802
self.assertBisectDirBlocks(expected,
1803
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1804
self.assertBisectDirBlocks(expected,
1805
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1806
self.assertBisectDirBlocks(expected,
1807
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1808
['b/c', 'b/c2', 'b/d', 'b/d2'],
1809
['b/d/e', 'b/d/e2'],
1810
], state, ['', 'b', 'b/d'])
1812
def test_bisect_dirblocks_missing(self):
1813
tree, state, expected = self.create_basic_dirstate()
1814
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1815
state, ['b/d', 'b/e'])
1816
# Files don't show up in this search
1817
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1818
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1819
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1820
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1821
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1823
def test_bisect_recursive_each(self):
1824
tree, state, expected = self.create_basic_dirstate()
1825
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1826
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1827
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1828
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1830
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1832
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1836
def test_bisect_recursive_multiple(self):
1837
tree, state, expected = self.create_basic_dirstate()
1838
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1839
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1840
state, ['b/d', 'b/d/e'])
1842
def test_bisect_recursive_missing(self):
1843
tree, state, expected = self.create_basic_dirstate()
1844
self.assertBisectRecursive(expected, [], state, ['d'])
1845
self.assertBisectRecursive(expected, [], state, ['b/e'])
1846
self.assertBisectRecursive(expected, [], state, ['g'])
1847
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1849
def test_bisect_recursive_renamed(self):
1850
tree, state, expected = self.create_renamed_dirstate()
1852
# Looking for either renamed item should find the other
1853
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1854
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1855
# Looking in the containing directory should find the rename target,
1856
# and anything in a subdir of the renamed target.
1857
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1858
'b/d/e', 'b/g', 'h', 'h/e'],
1862
class TestBisectDirblock(TestCase):
1863
"""Test that bisect_dirblock() returns the expected values.
1865
bisect_dirblock is intended to work like bisect.bisect_left() except it
1866
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1867
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1870
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1871
"""Assert that bisect_split works like bisect_left on the split paths.
1873
:param dirblocks: A list of (path, [info]) pairs.
1874
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1875
:param path: The path we are indexing.
1877
All other arguments will be passed along.
1879
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1881
split_dirblock = (path.split('/'), [])
1882
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1884
self.assertEqual(bisect_left_idx, bisect_split_idx,
1885
'bisect_split disagreed. %s != %s'
1887
% (bisect_left_idx, bisect_split_idx, path)
1890
def paths_to_dirblocks(self, paths):
1891
"""Convert a list of paths into dirblock form.
1893
Also, ensure that the paths are in proper sorted order.
1895
dirblocks = [(path, []) for path in paths]
1896
split_dirblocks = [(path.split('/'), []) for path in paths]
1897
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1898
return dirblocks, split_dirblocks
1900
def test_simple(self):
1901
"""In the simple case it works just like bisect_left"""
1902
paths = ['', 'a', 'b', 'c', 'd']
1903
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1905
self.assertBisect(dirblocks, split_dirblocks, path)
1906
self.assertBisect(dirblocks, split_dirblocks, '_')
1907
self.assertBisect(dirblocks, split_dirblocks, 'aa')
1908
self.assertBisect(dirblocks, split_dirblocks, 'bb')
1909
self.assertBisect(dirblocks, split_dirblocks, 'cc')
1910
self.assertBisect(dirblocks, split_dirblocks, 'dd')
1911
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1912
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1913
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1914
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1916
def test_involved(self):
1917
"""This is where bisect_left diverges slightly."""
1919
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1920
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1922
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1923
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1926
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1928
self.assertBisect(dirblocks, split_dirblocks, path)
1930
def test_involved_cached(self):
1931
"""This is where bisect_left diverges slightly."""
1933
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1934
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1936
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1937
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1941
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1943
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)