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.addCleanup(state.unlock)
795
self.assertRaises(errors.BzrError,
796
state.add, '.', 'ass-id', 'directory', None, None)
797
self.assertRaises(errors.BzrError,
798
state.add, '..', 'ass-id', 'directory', None, None)
801
class TestGetLines(TestCaseWithDirState):
803
def test_get_line_with_2_rows(self):
804
state = self.create_dirstate_with_root_and_subdir()
806
self.assertEqual(['#bazaar dirstate flat format 3\n',
807
'adler32: -1327947603\n',
811
'\x00\x00a-root-value\x00'
812
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
813
'\x00subdir\x00subdir-id\x00'
814
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
815
], state.get_lines())
819
def test_entry_to_line(self):
820
state = self.create_dirstate_with_root()
823
'\x00\x00a-root-value\x00d\x00\x000\x00n'
824
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
825
state._entry_to_line(state._dirblocks[0][1][0]))
829
def test_entry_to_line_with_parent(self):
830
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
831
root_entry = ('', '', 'a-root-value'), [
832
('d', '', 0, False, packed_stat), # current tree details
833
# first: a pointer to the current location
834
('a', 'dirname/basename', 0, False, ''),
836
state = dirstate.DirState.initialize('dirstate')
839
'\x00\x00a-root-value\x00'
840
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
841
'a\x00dirname/basename\x000\x00n\x00',
842
state._entry_to_line(root_entry))
846
def test_entry_to_line_with_two_parents_at_different_paths(self):
847
# / in the tree, at / in one parent and /dirname/basename in the other.
848
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
849
root_entry = ('', '', 'a-root-value'), [
850
('d', '', 0, False, packed_stat), # current tree details
851
('d', '', 0, False, 'rev_id'), # first parent details
852
# second: a pointer to the current location
853
('a', 'dirname/basename', 0, False, ''),
855
state = dirstate.DirState.initialize('dirstate')
858
'\x00\x00a-root-value\x00'
859
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
860
'd\x00\x000\x00n\x00rev_id\x00'
861
'a\x00dirname/basename\x000\x00n\x00',
862
state._entry_to_line(root_entry))
866
def test_iter_entries(self):
867
# we should be able to iterate the dirstate entries from end to end
868
# this is for get_lines to be easy to read.
869
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
871
root_entries = [(('', '', 'a-root-value'), [
872
('d', '', 0, False, packed_stat), # current tree details
874
dirblocks.append(('', root_entries))
875
# add two files in the root
876
subdir_entry = ('', 'subdir', 'subdir-id'), [
877
('d', '', 0, False, packed_stat), # current tree details
879
afile_entry = ('', 'afile', 'afile-id'), [
880
('f', 'sha1value', 34, False, packed_stat), # current tree details
882
dirblocks.append(('', [subdir_entry, afile_entry]))
884
file_entry2 = ('subdir', '2file', '2file-id'), [
885
('f', 'sha1value', 23, False, packed_stat), # current tree details
887
dirblocks.append(('subdir', [file_entry2]))
888
state = dirstate.DirState.initialize('dirstate')
890
state._set_data([], dirblocks)
891
expected_entries = [root_entries[0], subdir_entry, afile_entry,
893
self.assertEqual(expected_entries, list(state._iter_entries()))
898
class TestGetBlockRowIndex(TestCaseWithDirState):
900
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
901
file_present, state, dirname, basename, tree_index):
902
self.assertEqual((block_index, row_index, dir_present, file_present),
903
state._get_block_entry_index(dirname, basename, tree_index))
905
block = state._dirblocks[block_index]
906
self.assertEqual(dirname, block[0])
907
if dir_present and file_present:
908
row = state._dirblocks[block_index][1][row_index]
909
self.assertEqual(dirname, row[0][0])
910
self.assertEqual(basename, row[0][1])
912
def test_simple_structure(self):
913
state = self.create_dirstate_with_root_and_subdir()
914
self.addCleanup(state.unlock)
915
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
916
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
917
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
918
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
919
self.assertBlockRowIndexEqual(2, 0, False, False, state,
922
def test_complex_structure_exists(self):
923
state = self.create_complex_dirstate()
924
self.addCleanup(state.unlock)
925
# Make sure we can find everything that exists
926
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
927
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
928
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
929
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
930
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
931
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
932
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
933
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
934
self.assertBlockRowIndexEqual(3, 1, True, True, state,
937
def test_complex_structure_missing(self):
938
state = self.create_complex_dirstate()
939
self.addCleanup(state.unlock)
940
# Make sure things would be inserted in the right locations
941
# '_' comes before 'a'
942
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
943
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
944
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
945
self.assertBlockRowIndexEqual(1, 4, True, False, state,
947
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
948
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
949
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
950
# This would be inserted between a/ and b/
951
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
953
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
956
class TestGetEntry(TestCaseWithDirState):
958
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
959
"""Check that the right entry is returned for a request to getEntry."""
960
entry = state._get_entry(index, path_utf8=path)
962
self.assertEqual((None, None), entry)
965
self.assertEqual((dirname, basename, file_id), cur[:3])
967
def test_simple_structure(self):
968
state = self.create_dirstate_with_root_and_subdir()
969
self.addCleanup(state.unlock)
970
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
971
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
972
self.assertEntryEqual(None, None, None, state, 'missing', 0)
973
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
974
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
976
def test_complex_structure_exists(self):
977
state = self.create_complex_dirstate()
978
self.addCleanup(state.unlock)
979
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
980
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
981
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
982
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
983
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
984
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
985
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
986
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
987
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
990
def test_complex_structure_missing(self):
991
state = self.create_complex_dirstate()
992
self.addCleanup(state.unlock)
993
self.assertEntryEqual(None, None, None, state, '_', 0)
994
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
995
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
996
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
998
def test_get_entry_uninitialized(self):
999
"""Calling get_entry will load data if it needs to"""
1000
state = self.create_dirstate_with_root()
1006
state = dirstate.DirState.on_file('dirstate')
1009
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1010
state._header_state)
1011
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1012
state._dirblock_state)
1013
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1018
class TestDirstateSortOrder(TestCaseWithTransport):
1019
"""Test that DirState adds entries in the right order."""
1021
def test_add_sorting(self):
1022
"""Add entries in lexicographical order, we get path sorted order.
1024
This tests it to a depth of 4, to make sure we don't just get it right
1025
at a single depth. 'a/a' should come before 'a-a', even though it
1026
doesn't lexicographically.
1028
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1029
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1032
state = dirstate.DirState.initialize('dirstate')
1033
self.addCleanup(state.unlock)
1035
fake_stat = os.stat('dirstate')
1037
d_id = d.replace('/', '_')+'-id'
1038
file_path = d + '/f'
1039
file_id = file_path.replace('/', '_')+'-id'
1040
state.add(d, d_id, 'directory', fake_stat, null_sha)
1041
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1043
expected = ['', '', 'a',
1044
'a/a', 'a/a/a', 'a/a/a/a',
1045
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1047
split = lambda p:p.split('/')
1048
self.assertEqual(sorted(expected, key=split), expected)
1049
dirblock_names = [d[0] for d in state._dirblocks]
1050
self.assertEqual(expected, dirblock_names)
1052
def test_set_parent_trees_correct_order(self):
1053
"""After calling set_parent_trees() we should maintain the order."""
1054
dirs = ['a', 'a-a', 'a/a']
1056
state = dirstate.DirState.initialize('dirstate')
1057
self.addCleanup(state.unlock)
1059
fake_stat = os.stat('dirstate')
1061
d_id = d.replace('/', '_')+'-id'
1062
file_path = d + '/f'
1063
file_id = file_path.replace('/', '_')+'-id'
1064
state.add(d, d_id, 'directory', fake_stat, null_sha)
1065
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1067
expected = ['', '', 'a', 'a/a', 'a-a']
1068
dirblock_names = [d[0] for d in state._dirblocks]
1069
self.assertEqual(expected, dirblock_names)
1071
# *really* cheesy way to just get an empty tree
1072
repo = self.make_repository('repo')
1073
empty_tree = repo.revision_tree(None)
1074
state.set_parent_trees([('null:', empty_tree)], [])
1076
dirblock_names = [d[0] for d in state._dirblocks]
1077
self.assertEqual(expected, dirblock_names)
1080
class InstrumentedDirState(dirstate.DirState):
1081
"""An DirState with instrumented sha1 functionality."""
1083
def __init__(self, path):
1084
super(InstrumentedDirState, self).__init__(path)
1085
self._time_offset = 0
1088
def _sha_cutoff_time(self):
1089
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1090
self._cutoff_time = timestamp + self._time_offset
1092
def _sha1_file(self, abspath, entry):
1093
self._log.append(('sha1', abspath))
1094
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1096
def _read_link(self, abspath, old_link):
1097
self._log.append(('read_link', abspath, old_link))
1098
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1100
def _lstat(self, abspath, entry):
1101
self._log.append(('lstat', abspath))
1102
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1104
def _is_executable(self, mode, old_executable):
1105
self._log.append(('is_exec', mode, old_executable))
1106
return super(InstrumentedDirState, self)._is_executable(mode,
1109
def adjust_time(self, secs):
1110
"""Move the clock forward or back.
1112
:param secs: The amount to adjust the clock by. Positive values make it
1113
seem as if we are in the future, negative values make it seem like we
1116
self._time_offset += secs
1117
self._cutoff_time = None
1120
class _FakeStat(object):
1121
"""A class with the same attributes as a real stat result."""
1123
def __init__(self, size, mtime, ctime, dev, ino, mode):
1125
self.st_mtime = mtime
1126
self.st_ctime = ctime
1132
class TestUpdateEntry(TestCaseWithDirState):
1133
"""Test the DirState.update_entry functions"""
1135
def get_state_with_a(self):
1136
"""Create a DirState tracking a single object named 'a'"""
1137
state = InstrumentedDirState.initialize('dirstate')
1138
self.addCleanup(state.unlock)
1139
state.add('a', 'a-id', 'file', None, '')
1140
entry = state._get_entry(0, path_utf8='a')
1143
def test_update_entry(self):
1144
state, entry = self.get_state_with_a()
1145
self.build_tree(['a'])
1146
# Add one where we don't provide the stat or sha already
1147
self.assertEqual(('', 'a', 'a-id'), entry[0])
1148
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1150
# Flush the buffers to disk
1152
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1153
state._dirblock_state)
1155
stat_value = os.lstat('a')
1156
packed_stat = dirstate.pack_stat(stat_value)
1157
link_or_sha1 = state.update_entry(entry, abspath='a',
1158
stat_value=stat_value)
1159
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1162
# The dirblock entry should be updated with the new info
1163
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1165
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1166
state._dirblock_state)
1167
mode = stat_value.st_mode
1168
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1171
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1172
state._dirblock_state)
1174
# If we do it again right away, we don't know if the file has changed
1175
# so we will re-read the file. Roll the clock back so the file is
1176
# guaranteed to look too new.
1177
state.adjust_time(-10)
1179
link_or_sha1 = state.update_entry(entry, abspath='a',
1180
stat_value=stat_value)
1181
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1182
('sha1', 'a'), ('is_exec', mode, False),
1184
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1186
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1187
state._dirblock_state)
1190
# However, if we move the clock forward so the file is considered
1191
# "stable", it should just returned the cached value.
1192
state.adjust_time(20)
1193
link_or_sha1 = state.update_entry(entry, abspath='a',
1194
stat_value=stat_value)
1195
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1197
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1198
('sha1', 'a'), ('is_exec', mode, False),
1201
def test_update_entry_no_stat_value(self):
1202
"""Passing the stat_value is optional."""
1203
state, entry = self.get_state_with_a()
1204
state.adjust_time(-10) # Make sure the file looks new
1205
self.build_tree(['a'])
1206
# Add one where we don't provide the stat or sha already
1207
link_or_sha1 = state.update_entry(entry, abspath='a')
1208
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1210
stat_value = os.lstat('a')
1211
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1212
('is_exec', stat_value.st_mode, False),
1215
def test_update_entry_symlink(self):
1216
"""Update entry should read symlinks."""
1217
if not osutils.has_symlinks():
1218
return # PlatformDeficiency / TestSkipped
1219
state, entry = self.get_state_with_a()
1221
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1222
state._dirblock_state)
1223
os.symlink('target', 'a')
1225
state.adjust_time(-10) # Make the symlink look new
1226
stat_value = os.lstat('a')
1227
packed_stat = dirstate.pack_stat(stat_value)
1228
link_or_sha1 = state.update_entry(entry, abspath='a',
1229
stat_value=stat_value)
1230
self.assertEqual('target', link_or_sha1)
1231
self.assertEqual([('read_link', 'a', '')], state._log)
1232
# Dirblock is updated
1233
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1235
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1236
state._dirblock_state)
1238
# Because the stat_value looks new, we should re-read the target
1239
link_or_sha1 = state.update_entry(entry, abspath='a',
1240
stat_value=stat_value)
1241
self.assertEqual('target', link_or_sha1)
1242
self.assertEqual([('read_link', 'a', ''),
1243
('read_link', 'a', 'target'),
1245
state.adjust_time(+20) # Skip into the future, all files look old
1246
link_or_sha1 = state.update_entry(entry, abspath='a',
1247
stat_value=stat_value)
1248
self.assertEqual('target', link_or_sha1)
1249
# There should not be a new read_link call.
1250
# (this is a weak assertion, because read_link is fairly inexpensive,
1251
# versus the number of symlinks that we would have)
1252
self.assertEqual([('read_link', 'a', ''),
1253
('read_link', 'a', 'target'),
1256
def test_update_entry_dir(self):
1257
state, entry = self.get_state_with_a()
1258
self.build_tree(['a/'])
1259
self.assertIs(None, state.update_entry(entry, 'a'))
1261
def create_and_test_file(self, state, entry):
1262
"""Create a file at 'a' and verify the state finds it.
1264
The state should already be versioning *something* at 'a'. This makes
1265
sure that state.update_entry recognizes it as a file.
1267
self.build_tree(['a'])
1268
stat_value = os.lstat('a')
1269
packed_stat = dirstate.pack_stat(stat_value)
1271
link_or_sha1 = state.update_entry(entry, abspath='a')
1272
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1274
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1278
def create_and_test_dir(self, state, entry):
1279
"""Create a directory at 'a' and verify the state finds it.
1281
The state should already be versioning *something* at 'a'. This makes
1282
sure that state.update_entry recognizes it as a directory.
1284
self.build_tree(['a/'])
1285
stat_value = os.lstat('a')
1286
packed_stat = dirstate.pack_stat(stat_value)
1288
link_or_sha1 = state.update_entry(entry, abspath='a')
1289
self.assertIs(None, link_or_sha1)
1290
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1294
def create_and_test_symlink(self, state, entry):
1295
"""Create a symlink at 'a' and verify the state finds it.
1297
The state should already be versioning *something* at 'a'. This makes
1298
sure that state.update_entry recognizes it as a symlink.
1300
This should not be called if this platform does not have symlink
1303
os.symlink('path/to/foo', 'a')
1305
stat_value = os.lstat('a')
1306
packed_stat = dirstate.pack_stat(stat_value)
1308
link_or_sha1 = state.update_entry(entry, abspath='a')
1309
self.assertEqual('path/to/foo', link_or_sha1)
1310
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1314
def test_update_missing_file(self):
1315
state, entry = self.get_state_with_a()
1316
packed_stat = self.create_and_test_file(state, entry)
1317
# Now if we delete the file, update_entry should recover and
1320
self.assertIs(None, state.update_entry(entry, abspath='a'))
1321
# And the record shouldn't be changed.
1322
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1323
self.assertEqual([('f', digest, 14, False, packed_stat)],
1326
def test_update_missing_dir(self):
1327
state, entry = self.get_state_with_a()
1328
packed_stat = self.create_and_test_dir(state, entry)
1329
# Now if we delete the directory, update_entry should recover and
1332
self.assertIs(None, state.update_entry(entry, abspath='a'))
1333
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1335
def test_update_missing_symlink(self):
1336
if not osutils.has_symlinks():
1337
return # PlatformDeficiency / TestSkipped
1338
state, entry = self.get_state_with_a()
1339
packed_stat = self.create_and_test_symlink(state, entry)
1341
self.assertIs(None, state.update_entry(entry, abspath='a'))
1342
# And the record shouldn't be changed.
1343
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1346
def test_update_file_to_dir(self):
1347
"""If a file changes to a directory we return None for the sha.
1348
We also update the inventory record.
1350
state, entry = self.get_state_with_a()
1351
self.create_and_test_file(state, entry)
1353
self.create_and_test_dir(state, entry)
1355
def test_update_file_to_symlink(self):
1356
"""File becomes a symlink"""
1357
if not osutils.has_symlinks():
1358
return # PlatformDeficiency / TestSkipped
1359
state, entry = self.get_state_with_a()
1360
self.create_and_test_file(state, entry)
1362
self.create_and_test_symlink(state, entry)
1364
def test_update_dir_to_file(self):
1365
"""Directory becoming a file updates the entry."""
1366
state, entry = self.get_state_with_a()
1367
self.create_and_test_dir(state, entry)
1369
self.create_and_test_file(state, entry)
1371
def test_update_dir_to_symlink(self):
1372
"""Directory becomes a symlink"""
1373
if not osutils.has_symlinks():
1374
return # PlatformDeficiency / TestSkipped
1375
state, entry = self.get_state_with_a()
1376
self.create_and_test_dir(state, entry)
1378
self.create_and_test_symlink(state, entry)
1380
def test_update_symlink_to_file(self):
1381
"""Symlink becomes a file"""
1382
state, entry = self.get_state_with_a()
1383
self.create_and_test_symlink(state, entry)
1385
self.create_and_test_file(state, entry)
1387
def test_update_symlink_to_dir(self):
1388
"""Symlink becomes a directory"""
1389
state, entry = self.get_state_with_a()
1390
self.create_and_test_symlink(state, entry)
1392
self.create_and_test_dir(state, entry)
1394
def test__is_executable_win32(self):
1395
state, entry = self.get_state_with_a()
1396
self.build_tree(['a'])
1398
# Make sure we are using the win32 implementation of _is_executable
1399
state._is_executable = state._is_executable_win32
1401
# The file on disk is not executable, but we are marking it as though
1402
# it is. With _is_executable_win32 we ignore what is on disk.
1403
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1405
stat_value = os.lstat('a')
1406
packed_stat = dirstate.pack_stat(stat_value)
1408
state.adjust_time(-10) # Make sure everything is new
1409
# Make sure it wants to kkkkkkkk
1410
state.update_entry(entry, abspath='a', stat_value=stat_value)
1412
# The row is updated, but the executable bit stays set.
1413
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1414
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1417
class TestPackStat(TestCaseWithTransport):
1419
def assertPackStat(self, expected, stat_value):
1420
"""Check the packed and serialized form of a stat value."""
1421
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1423
def test_pack_stat_int(self):
1424
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1425
# Make sure that all parameters have an impact on the packed stat.
1426
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1429
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1430
st.st_mtime = 1172758620
1432
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1433
st.st_ctime = 1172758630
1435
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1438
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1439
st.st_ino = 6499540L
1441
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1442
st.st_mode = 0100744
1444
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1446
def test_pack_stat_float(self):
1447
"""On some platforms mtime and ctime are floats.
1449
Make sure we don't get warnings or errors, and that we ignore changes <
1452
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1453
777L, 6499538L, 0100644)
1454
# These should all be the same as the integer counterparts
1455
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1456
st.st_mtime = 1172758620.0
1458
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1459
st.st_ctime = 1172758630.0
1461
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1462
# fractional seconds are discarded, so no change from above
1463
st.st_mtime = 1172758620.453
1464
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1465
st.st_ctime = 1172758630.228
1466
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1469
class TestBisect(TestCaseWithTransport):
1470
"""Test the ability to bisect into the disk format."""
1472
def create_basic_dirstate(self):
1473
"""Create a dirstate with a few files and directories.
1482
tree = self.make_branch_and_tree('tree')
1483
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1484
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1485
self.build_tree(['tree/' + p for p in paths])
1486
tree.set_root_id('TREE_ROOT')
1487
tree.add([p.rstrip('/') for p in paths], file_ids)
1488
tree.commit('initial', rev_id='rev-1')
1489
revision_id = 'rev-1'
1490
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1491
t = self.get_transport().clone('tree')
1492
a_text = t.get_bytes('a')
1493
a_sha = osutils.sha_string(a_text)
1495
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1496
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1497
c_text = t.get_bytes('b/c')
1498
c_sha = osutils.sha_string(c_text)
1500
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1501
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1502
e_text = t.get_bytes('b/d/e')
1503
e_sha = osutils.sha_string(e_text)
1505
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1506
f_text = t.get_bytes('f')
1507
f_sha = osutils.sha_string(f_text)
1509
null_stat = dirstate.DirState.NULLSTAT
1511
'':(('', '', 'TREE_ROOT'), [
1512
('d', '', 0, False, null_stat),
1513
('d', '', 0, False, revision_id),
1515
'a':(('', 'a', 'a-id'), [
1516
('f', '', 0, False, null_stat),
1517
('f', a_sha, a_len, False, revision_id),
1519
'b':(('', 'b', 'b-id'), [
1520
('d', '', 0, False, null_stat),
1521
('d', '', 0, False, revision_id),
1523
'b/c':(('b', 'c', 'c-id'), [
1524
('f', '', 0, False, null_stat),
1525
('f', c_sha, c_len, False, revision_id),
1527
'b/d':(('b', 'd', 'd-id'), [
1528
('d', '', 0, False, null_stat),
1529
('d', '', 0, False, revision_id),
1531
'b/d/e':(('b/d', 'e', 'e-id'), [
1532
('f', '', 0, False, null_stat),
1533
('f', e_sha, e_len, False, revision_id),
1535
'f':(('', 'f', 'f-id'), [
1536
('f', '', 0, False, null_stat),
1537
('f', f_sha, f_len, False, revision_id),
1540
state = dirstate.DirState.from_tree(tree, 'dirstate')
1545
# Use a different object, to make sure nothing is pre-cached in memory.
1546
state = dirstate.DirState.on_file('dirstate')
1548
self.addCleanup(state.unlock)
1549
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1550
state._dirblock_state)
1551
# This is code is only really tested if we actually have to make more
1552
# than one read, so set the page size to something smaller.
1553
# We want it to contain about 2.2 records, so that we have a couple
1554
# records that we can read per attempt
1555
state._bisect_page_size = 200
1556
return tree, state, expected
1558
def create_duplicated_dirstate(self):
1559
"""Create a dirstate with a deleted and added entries.
1561
This grabs a basic_dirstate, and then removes and re adds every entry
1564
tree, state, expected = self.create_basic_dirstate()
1565
# Now we will just remove and add every file so we get an extra entry
1566
# per entry. Unversion in reverse order so we handle subdirs
1567
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1568
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1569
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1571
# Update the expected dictionary.
1572
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1573
orig = expected[path]
1575
# This record was deleted in the current tree
1576
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1578
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1579
# And didn't exist in the basis tree
1580
expected[path2] = (new_key, [orig[1][0],
1581
dirstate.DirState.NULL_PARENT_DETAILS])
1583
# We will replace the 'dirstate' file underneath 'state', but that is
1584
# okay as lock as we unlock 'state' first.
1587
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1593
# But we need to leave state in a read-lock because we already have
1594
# a cleanup scheduled
1596
return tree, state, expected
1598
def create_renamed_dirstate(self):
1599
"""Create a dirstate with a few internal renames.
1601
This takes the basic dirstate, and moves the paths around.
1603
tree, state, expected = self.create_basic_dirstate()
1605
tree.rename_one('a', 'b/g')
1607
tree.rename_one('b/d', 'h')
1609
old_a = expected['a']
1610
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1611
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1612
('r', 'a', 0, False, '')])
1613
old_d = expected['b/d']
1614
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1615
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1616
('r', 'b/d', 0, False, '')])
1618
old_e = expected['b/d/e']
1619
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1621
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1622
('r', 'b/d/e', 0, False, '')])
1626
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1633
return tree, state, expected
1635
def assertBisect(self, expected_map, map_keys, state, paths):
1636
"""Assert that bisecting for paths returns the right result.
1638
:param expected_map: A map from key => entry value
1639
:param map_keys: The keys to expect for each path
1640
:param state: The DirState object.
1641
:param paths: A list of paths, these will automatically be split into
1642
(dir, name) tuples, and sorted according to how _bisect
1645
dir_names = sorted(osutils.split(p) for p in paths)
1646
result = state._bisect(dir_names)
1647
# For now, results are just returned in whatever order we read them.
1648
# We could sort by (dir, name, file_id) or something like that, but in
1649
# the end it would still be fairly arbitrary, and we don't want the
1650
# extra overhead if we can avoid it. So sort everything to make sure
1652
assert len(map_keys) == len(dir_names)
1654
for dir_name, keys in zip(dir_names, map_keys):
1656
# This should not be present in the output
1658
expected[dir_name] = sorted(expected_map[k] for k in keys)
1660
for dir_name in result:
1661
result[dir_name].sort()
1663
self.assertEqual(expected, result)
1665
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1666
"""Assert that bisecting for dirbblocks returns the right result.
1668
:param expected_map: A map from key => expected values
1669
:param map_keys: A nested list of paths we expect to be returned.
1670
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1671
:param state: The DirState object.
1672
:param paths: A list of directories
1674
result = state._bisect_dirblocks(paths)
1675
assert len(map_keys) == len(paths)
1678
for path, keys in zip(paths, map_keys):
1680
# This should not be present in the output
1682
expected[path] = sorted(expected_map[k] for k in keys)
1686
self.assertEqual(expected, result)
1688
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1689
"""Assert the return value of a recursive bisection.
1691
:param expected_map: A map from key => entry value
1692
:param map_keys: A list of paths we expect to be returned.
1693
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1694
:param state: The DirState object.
1695
:param paths: A list of files and directories. It will be broken up
1696
into (dir, name) pairs and sorted before calling _bisect_recursive.
1699
for key in map_keys:
1700
entry = expected_map[key]
1701
dir_name_id, trees_info = entry
1702
expected[dir_name_id] = trees_info
1704
dir_names = sorted(osutils.split(p) for p in paths)
1705
result = state._bisect_recursive(dir_names)
1707
self.assertEqual(expected, result)
1709
def test_bisect_each(self):
1710
"""Find a single record using bisect."""
1711
tree, state, expected = self.create_basic_dirstate()
1713
# Bisect should return the rows for the specified files.
1714
self.assertBisect(expected, [['']], state, [''])
1715
self.assertBisect(expected, [['a']], state, ['a'])
1716
self.assertBisect(expected, [['b']], state, ['b'])
1717
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1718
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1719
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1720
self.assertBisect(expected, [['f']], state, ['f'])
1722
def test_bisect_multi(self):
1723
"""Bisect can be used to find multiple records at the same time."""
1724
tree, state, expected = self.create_basic_dirstate()
1725
# Bisect should be capable of finding multiple entries at the same time
1726
self.assertBisect(expected, [['a'], ['b'], ['f']],
1727
state, ['a', 'b', 'f'])
1728
# ('', 'f') sorts before the others
1729
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1730
state, ['b/d', 'b/d/e', 'f'])
1732
def test_bisect_one_page(self):
1733
"""Test bisect when there is only 1 page to read"""
1734
tree, state, expected = self.create_basic_dirstate()
1735
state._bisect_page_size = 5000
1736
self.assertBisect(expected,[['']], state, [''])
1737
self.assertBisect(expected,[['a']], state, ['a'])
1738
self.assertBisect(expected,[['b']], state, ['b'])
1739
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1740
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1741
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1742
self.assertBisect(expected,[['f']], state, ['f'])
1743
self.assertBisect(expected,[['a'], ['b'], ['f']],
1744
state, ['a', 'b', 'f'])
1745
# ('', 'f') sorts before the others
1746
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1747
state, ['b/d', 'b/d/e', 'f'])
1749
def test_bisect_duplicate_paths(self):
1750
"""When bisecting for a path, handle multiple entries."""
1751
tree, state, expected = self.create_duplicated_dirstate()
1753
# Now make sure that both records are properly returned.
1754
self.assertBisect(expected, [['']], state, [''])
1755
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1756
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1757
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1758
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1759
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1761
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1763
def test_bisect_page_size_too_small(self):
1764
"""If the page size is too small, we will auto increase it."""
1765
tree, state, expected = self.create_basic_dirstate()
1766
state._bisect_page_size = 50
1767
self.assertBisect(expected, [None], state, ['b/e'])
1768
self.assertBisect(expected, [['a']], state, ['a'])
1769
self.assertBisect(expected, [['b']], state, ['b'])
1770
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1771
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1772
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1773
self.assertBisect(expected, [['f']], state, ['f'])
1775
def test_bisect_missing(self):
1776
"""Test that bisect return None if it cannot find a path."""
1777
tree, state, expected = self.create_basic_dirstate()
1778
self.assertBisect(expected, [None], state, ['foo'])
1779
self.assertBisect(expected, [None], state, ['b/foo'])
1780
self.assertBisect(expected, [None], state, ['bar/foo'])
1782
self.assertBisect(expected, [['a'], None, ['b/d']],
1783
state, ['a', 'foo', 'b/d'])
1785
def test_bisect_rename(self):
1786
"""Check that we find a renamed row."""
1787
tree, state, expected = self.create_renamed_dirstate()
1789
# Search for the pre and post renamed entries
1790
self.assertBisect(expected, [['a']], state, ['a'])
1791
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1792
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1793
self.assertBisect(expected, [['h']], state, ['h'])
1795
# What about b/d/e? shouldn't that also get 2 directory entries?
1796
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1797
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1799
def test_bisect_dirblocks(self):
1800
tree, state, expected = self.create_duplicated_dirstate()
1801
self.assertBisectDirBlocks(expected,
1802
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1803
self.assertBisectDirBlocks(expected,
1804
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1805
self.assertBisectDirBlocks(expected,
1806
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1807
self.assertBisectDirBlocks(expected,
1808
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1809
['b/c', 'b/c2', 'b/d', 'b/d2'],
1810
['b/d/e', 'b/d/e2'],
1811
], state, ['', 'b', 'b/d'])
1813
def test_bisect_dirblocks_missing(self):
1814
tree, state, expected = self.create_basic_dirstate()
1815
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1816
state, ['b/d', 'b/e'])
1817
# Files don't show up in this search
1818
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1819
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1820
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1821
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1822
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1824
def test_bisect_recursive_each(self):
1825
tree, state, expected = self.create_basic_dirstate()
1826
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1827
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1828
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1829
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1831
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1833
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1837
def test_bisect_recursive_multiple(self):
1838
tree, state, expected = self.create_basic_dirstate()
1839
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1840
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1841
state, ['b/d', 'b/d/e'])
1843
def test_bisect_recursive_missing(self):
1844
tree, state, expected = self.create_basic_dirstate()
1845
self.assertBisectRecursive(expected, [], state, ['d'])
1846
self.assertBisectRecursive(expected, [], state, ['b/e'])
1847
self.assertBisectRecursive(expected, [], state, ['g'])
1848
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1850
def test_bisect_recursive_renamed(self):
1851
tree, state, expected = self.create_renamed_dirstate()
1853
# Looking for either renamed item should find the other
1854
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1855
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1856
# Looking in the containing directory should find the rename target,
1857
# and anything in a subdir of the renamed target.
1858
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1859
'b/d/e', 'b/g', 'h', 'h/e'],
1863
class TestBisectDirblock(TestCase):
1864
"""Test that bisect_dirblock() returns the expected values.
1866
bisect_dirblock is intended to work like bisect.bisect_left() except it
1867
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1868
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1871
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1872
"""Assert that bisect_split works like bisect_left on the split paths.
1874
:param dirblocks: A list of (path, [info]) pairs.
1875
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1876
:param path: The path we are indexing.
1878
All other arguments will be passed along.
1880
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1882
split_dirblock = (path.split('/'), [])
1883
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1885
self.assertEqual(bisect_left_idx, bisect_split_idx,
1886
'bisect_split disagreed. %s != %s'
1888
% (bisect_left_idx, bisect_split_idx, path)
1891
def paths_to_dirblocks(self, paths):
1892
"""Convert a list of paths into dirblock form.
1894
Also, ensure that the paths are in proper sorted order.
1896
dirblocks = [(path, []) for path in paths]
1897
split_dirblocks = [(path.split('/'), []) for path in paths]
1898
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1899
return dirblocks, split_dirblocks
1901
def test_simple(self):
1902
"""In the simple case it works just like bisect_left"""
1903
paths = ['', 'a', 'b', 'c', 'd']
1904
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1906
self.assertBisect(dirblocks, split_dirblocks, path)
1907
self.assertBisect(dirblocks, split_dirblocks, '_')
1908
self.assertBisect(dirblocks, split_dirblocks, 'aa')
1909
self.assertBisect(dirblocks, split_dirblocks, 'bb')
1910
self.assertBisect(dirblocks, split_dirblocks, 'cc')
1911
self.assertBisect(dirblocks, split_dirblocks, 'dd')
1912
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1913
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1914
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1915
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1917
def test_involved(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',
1927
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1929
self.assertBisect(dirblocks, split_dirblocks, path)
1931
def test_involved_cached(self):
1932
"""This is where bisect_left diverges slightly."""
1934
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1935
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1937
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1938
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1942
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1944
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)