~bzr-pqm/bzr/bzr.dev

2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
# Authors:  Robert Collins <robert.collins@canonical.com>
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
17
18
"""Tests for WorkingTreeFormat4"""
19
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
20
import os
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
21
import time
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
22
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
23
from bzrlib import (
24
    bzrdir,
25
    dirstate,
26
    errors,
1551.15.6 by Aaron Bentley
Use ROOT_ID when the repository supports old clients (Bug #107168)
27
    inventory,
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
28
    osutils,
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
29
    workingtree_4,
30
    )
31
from bzrlib.lockdir import LockDir
32
from bzrlib.tests import TestCaseWithTransport, TestSkipped
33
from bzrlib.tree import InterTree
34
35
36
class TestWorkingTreeFormat4(TestCaseWithTransport):
37
    """Tests specific to WorkingTreeFormat4."""
38
39
    def test_disk_layout(self):
40
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
41
        control.create_repository()
42
        control.create_branch()
43
        tree = workingtree_4.WorkingTreeFormat4().initialize(control)
44
        # we want:
45
        # format 'Bazaar Working Tree format 4'
46
        # stat-cache = ??
47
        t = control.get_workingtree_transport(None)
2255.2.230 by Robert Collins
Update tree format signatures to mention introducing bzr version.
48
        self.assertEqualDiff('Bazaar Working Tree Format 4 (bzr 0.15)\n',
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
49
                             t.get('format').read())
50
        self.assertFalse(t.has('inventory.basis'))
51
        # no last-revision file means 'None' or 'NULLREVISION'
52
        self.assertFalse(t.has('last-revision'))
53
        state = dirstate.DirState.on_file(t.local_abspath('dirstate'))
54
        state.lock_read()
55
        try:
56
            self.assertEqual([], state.get_parent_ids())
57
        finally:
58
            state.unlock()
59
60
    def test_uses_lockdir(self):
61
        """WorkingTreeFormat4 uses its own LockDir:
2255.10.1 by John Arbash Meinel
Update WorkingTree4 so that it doesn't use a HashCache,
62
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
63
            - lock is a directory
64
            - when the WorkingTree is locked, LockDir can see that
65
        """
66
        # this test could be factored into a subclass of tests common to both
4285.2.1 by Vincent Ladeuil
Cleanup test imports and use features to better track skipped tests.
67
        # format 3 and 4, but for now its not much of an issue as there is only
68
        # one in common.
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
69
        t = self.get_transport()
70
        tree = self.make_workingtree()
71
        self.assertIsDirectory('.bzr', t)
72
        self.assertIsDirectory('.bzr/checkout', t)
73
        self.assertIsDirectory('.bzr/checkout/lock', t)
74
        our_lock = LockDir(t, '.bzr/checkout/lock')
75
        self.assertEquals(our_lock.peek(), None)
76
        tree.lock_write()
77
        self.assertTrue(our_lock.peek())
78
        tree.unlock()
79
        self.assertEquals(our_lock.peek(), None)
80
81
    def make_workingtree(self, relpath=''):
82
        url = self.get_url(relpath)
83
        if relpath:
84
            self.build_tree([relpath + '/'])
85
        dir = bzrdir.BzrDirMetaFormat1().initialize(url)
86
        repo = dir.create_repository()
87
        branch = dir.create_branch()
88
        try:
89
            return workingtree_4.WorkingTreeFormat4().initialize(dir)
90
        except errors.NotLocalUrl:
91
            raise TestSkipped('Not a local URL')
92
93
    def test_dirstate_stores_all_parent_inventories(self):
94
        tree = self.make_workingtree()
95
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
96
        # We're going to build in tree a working tree
97
        # with three parent trees, with some files in common.
98
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
99
        # We really don't want to do commit or merge in the new dirstate-based
100
        # tree, because that might not work yet.  So instead we build
101
        # revisions elsewhere and pull them across, doing by hand part of the
102
        # work that merge would do.
103
104
        subtree = self.make_branch_and_tree('subdir')
105
        # writelock the tree so its repository doesn't get readlocked by
106
        # the revision tree locks. This works around the bug where we dont
107
        # permit lock upgrading.
108
        subtree.lock_write()
109
        self.addCleanup(subtree.unlock)
110
        self.build_tree(['subdir/file-a',])
111
        subtree.add(['file-a'], ['id-a'])
112
        rev1 = subtree.commit('commit in subdir')
113
114
        subtree2 = subtree.bzrdir.sprout('subdir2').open_workingtree()
115
        self.build_tree(['subdir2/file-b'])
116
        subtree2.add(['file-b'], ['id-b'])
117
        rev2 = subtree2.commit('commit in subdir2')
118
119
        subtree.flush()
3462.1.7 by John Arbash Meinel
fix a test that assumed WT4.set_parent_trees() wouldn't filter the list.
120
        subtree3 = subtree.bzrdir.sprout('subdir3').open_workingtree()
121
        rev3 = subtree3.commit('merge from subdir2')
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
122
123
        repo = tree.branch.repository
3462.1.7 by John Arbash Meinel
fix a test that assumed WT4.set_parent_trees() wouldn't filter the list.
124
        repo.fetch(subtree.branch.repository, rev1)
125
        repo.fetch(subtree2.branch.repository, rev2)
126
        repo.fetch(subtree3.branch.repository, rev3)
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
127
        # will also pull the others...
128
129
        # create repository based revision trees
3462.1.7 by John Arbash Meinel
fix a test that assumed WT4.set_parent_trees() wouldn't filter the list.
130
        rev1_revtree = repo.revision_tree(rev1)
131
        rev2_revtree = repo.revision_tree(rev2)
132
        rev3_revtree = repo.revision_tree(rev3)
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
133
        # tree doesn't contain a text merge yet but we'll just
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
134
        # set the parents as if a merge had taken place.
135
        # this should cause the tree data to be folded into the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
136
        # dirstate.
137
        tree.set_parent_trees([
138
            (rev1, rev1_revtree),
139
            (rev2, rev2_revtree),
140
            (rev3, rev3_revtree), ])
141
142
        # create tree-sourced revision trees
143
        rev1_tree = tree.revision_tree(rev1)
144
        rev1_tree.lock_read()
145
        self.addCleanup(rev1_tree.unlock)
146
        rev2_tree = tree.revision_tree(rev2)
147
        rev2_tree.lock_read()
148
        self.addCleanup(rev2_tree.unlock)
149
        rev3_tree = tree.revision_tree(rev3)
150
        rev3_tree.lock_read()
151
        self.addCleanup(rev3_tree.unlock)
152
153
        # now we should be able to get them back out
154
        self.assertTreesEqual(rev1_revtree, rev1_tree)
155
        self.assertTreesEqual(rev2_revtree, rev2_tree)
156
        self.assertTreesEqual(rev3_revtree, rev3_tree)
157
158
    def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
159
        """Setting parent trees on a dirstate working tree takes
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
160
        the trees it's given and doesn't need to read them from the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
161
        repository.
162
        """
163
        tree = self.make_workingtree()
164
165
        subtree = self.make_branch_and_tree('subdir')
166
        rev1 = subtree.commit('commit in subdir')
167
        rev1_tree = subtree.basis_tree()
168
        rev1_tree.lock_read()
169
        self.addCleanup(rev1_tree.unlock)
170
171
        tree.branch.pull(subtree.branch)
172
173
        # break the repository's legs to make sure it only uses the trees
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
174
        # it's given; any calls to forbidden methods will raise an
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
175
        # AssertionError
176
        repo = tree.branch.repository
177
        repo.get_revision = self.fail
178
        repo.get_inventory = self.fail
179
        repo.get_inventory_xml = self.fail
180
        # try to set the parent trees.
181
        tree.set_parent_trees([(rev1, rev1_tree)])
182
183
    def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
184
        """Getting parent trees from a dirstate tree does not read from the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
185
        repos inventory store. This is an important part of the dirstate
186
        performance optimisation work.
187
        """
188
        tree = self.make_workingtree()
189
190
        subtree = self.make_branch_and_tree('subdir')
191
        # writelock the tree so its repository doesn't get readlocked by
192
        # the revision tree locks. This works around the bug where we dont
193
        # permit lock upgrading.
194
        subtree.lock_write()
195
        self.addCleanup(subtree.unlock)
196
        rev1 = subtree.commit('commit in subdir')
197
        rev1_tree = subtree.basis_tree()
198
        rev1_tree.lock_read()
199
        rev1_tree.inventory
200
        self.addCleanup(rev1_tree.unlock)
201
        rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
202
        rev2_tree = subtree.basis_tree()
203
        rev2_tree.lock_read()
204
        rev2_tree.inventory
205
        self.addCleanup(rev2_tree.unlock)
206
207
        tree.branch.pull(subtree.branch)
208
209
        # break the repository's legs to make sure it only uses the trees
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
210
        # it's given; any calls to forbidden methods will raise an
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
211
        # AssertionError
212
        repo = tree.branch.repository
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
213
        # dont uncomment this: the revision object must be accessed to
214
        # answer 'get_parent_ids' for the revision tree- dirstate does not
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
215
        # cache the parents of a parent tree at this point.
216
        #repo.get_revision = self.fail
217
        repo.get_inventory = self.fail
218
        repo.get_inventory_xml = self.fail
219
        # set the parent trees.
220
        tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
221
        # read the first tree
222
        result_rev1_tree = tree.revision_tree(rev1)
223
        # read the second
224
        result_rev2_tree = tree.revision_tree(rev2)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
225
        # compare - there should be no differences between the handed and
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
226
        # returned trees
227
        self.assertTreesEqual(rev1_tree, result_rev1_tree)
228
        self.assertTreesEqual(rev2_tree, result_rev2_tree)
229
230
    def test_dirstate_doesnt_cache_non_parent_trees(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
231
        """Getting parent trees from a dirstate tree does not read from the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
232
        repos inventory store. This is an important part of the dirstate
233
        performance optimisation work.
234
        """
235
        tree = self.make_workingtree()
236
237
        # make a tree that we can try for, which is able to be returned but
238
        # must not be
239
        subtree = self.make_branch_and_tree('subdir')
240
        rev1 = subtree.commit('commit in subdir')
241
        tree.branch.pull(subtree.branch)
242
        # check it fails
243
        self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
244
245
    def test_no_dirstate_outside_lock(self):
246
        # temporary test until the code is mature enough to test from outside.
247
        """Getting a dirstate object fails if there is no lock."""
248
        def lock_and_call_current_dirstate(tree, lock_method):
249
            getattr(tree, lock_method)()
250
            tree.current_dirstate()
251
            tree.unlock()
252
        tree = self.make_workingtree()
253
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
254
        lock_and_call_current_dirstate(tree, 'lock_read')
255
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
256
        lock_and_call_current_dirstate(tree, 'lock_write')
257
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
258
        lock_and_call_current_dirstate(tree, 'lock_tree_write')
259
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
260
261
    def test_new_dirstate_on_new_lock(self):
262
        # until we have detection for when a dirstate can be reused, we
263
        # want to reparse dirstate on every new lock.
264
        known_dirstates = set()
265
        def lock_and_compare_all_current_dirstate(tree, lock_method):
266
            getattr(tree, lock_method)()
267
            state = tree.current_dirstate()
268
            self.assertFalse(state in known_dirstates)
269
            known_dirstates.add(state)
270
            tree.unlock()
271
        tree = self.make_workingtree()
272
        # lock twice with each type to prevent silly per-lock-type bugs.
273
        # each lock and compare looks for a unique state object.
274
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
275
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
276
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
277
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
278
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
279
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
280
2255.2.122 by Robert Collins
Alter intertree implementation tests to let dirstate inter-trees be correctly parameterised.
281
    def test_constructing_invalid_interdirstate_raises(self):
282
        tree = self.make_workingtree()
283
        rev_id = tree.commit('first post')
284
        rev_id2 = tree.commit('second post')
285
        rev_tree = tree.branch.repository.revision_tree(rev_id)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
286
        # Exception is not a great thing to raise, but this test is
287
        # very short, and code is used to sanity check other tests, so
2255.2.122 by Robert Collins
Alter intertree implementation tests to let dirstate inter-trees be correctly parameterised.
288
        # a full error object is YAGNI.
289
        self.assertRaises(
290
            Exception, workingtree_4.InterDirStateTree, rev_tree, tree)
291
        self.assertRaises(
292
            Exception, workingtree_4.InterDirStateTree, tree, rev_tree)
293
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
294
    def test_revtree_to_revtree_not_interdirstate(self):
295
        # we should not get a dirstate optimiser for two repository sourced
296
        # revtrees. we can't prove a negative, so we dont do exhaustive tests
297
        # of all formats; though that could be written in the future it doesn't
298
        # seem well worth it.
299
        tree = self.make_workingtree()
300
        rev_id = tree.commit('first post')
301
        rev_id2 = tree.commit('second post')
302
        rev_tree = tree.branch.repository.revision_tree(rev_id)
303
        rev_tree2 = tree.branch.repository.revision_tree(rev_id2)
304
        optimiser = InterTree.get(rev_tree, rev_tree2)
305
        self.assertIsInstance(optimiser, InterTree)
306
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
307
        optimiser = InterTree.get(rev_tree2, rev_tree)
308
        self.assertIsInstance(optimiser, InterTree)
309
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
310
311
    def test_revtree_not_in_dirstate_to_dirstate_not_interdirstate(self):
312
        # we should not get a dirstate optimiser when the revision id for of
313
        # the source is not in the dirstate of the target.
314
        tree = self.make_workingtree()
315
        rev_id = tree.commit('first post')
316
        rev_id2 = tree.commit('second post')
317
        rev_tree = tree.branch.repository.revision_tree(rev_id)
318
        tree.lock_read()
319
        optimiser = InterTree.get(rev_tree, tree)
320
        self.assertIsInstance(optimiser, InterTree)
321
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
322
        optimiser = InterTree.get(tree, rev_tree)
323
        self.assertIsInstance(optimiser, InterTree)
324
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
325
        tree.unlock()
326
327
    def test_empty_basis_to_dirstate_tree(self):
328
        # we should get a InterDirStateTree for doing
329
        # 'changes_from' from the first basis dirstate revision tree to a
330
        # WorkingTree4.
331
        tree = self.make_workingtree()
332
        tree.lock_read()
333
        basis_tree = tree.basis_tree()
334
        basis_tree.lock_read()
335
        optimiser = InterTree.get(basis_tree, tree)
336
        tree.unlock()
337
        basis_tree.unlock()
338
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
339
340
    def test_nonempty_basis_to_dirstate_tree(self):
341
        # we should get a InterDirStateTree for doing
342
        # 'changes_from' from a non-null basis dirstate revision tree to a
343
        # WorkingTree4.
344
        tree = self.make_workingtree()
345
        tree.commit('first post')
346
        tree.lock_read()
347
        basis_tree = tree.basis_tree()
348
        basis_tree.lock_read()
349
        optimiser = InterTree.get(basis_tree, tree)
350
        tree.unlock()
351
        basis_tree.unlock()
352
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
353
354
    def test_empty_basis_revtree_to_dirstate_tree(self):
355
        # we should get a InterDirStateTree for doing
356
        # 'changes_from' from an empty repository based rev tree to a
357
        # WorkingTree4.
358
        tree = self.make_workingtree()
359
        tree.lock_read()
360
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
361
        basis_tree.lock_read()
362
        optimiser = InterTree.get(basis_tree, tree)
363
        tree.unlock()
364
        basis_tree.unlock()
365
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
366
367
    def test_nonempty_basis_revtree_to_dirstate_tree(self):
368
        # we should get a InterDirStateTree for doing
369
        # 'changes_from' from a non-null repository based rev tree to a
370
        # WorkingTree4.
371
        tree = self.make_workingtree()
372
        tree.commit('first post')
373
        tree.lock_read()
374
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
375
        basis_tree.lock_read()
376
        optimiser = InterTree.get(basis_tree, tree)
377
        tree.unlock()
378
        basis_tree.unlock()
379
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
380
381
    def test_tree_to_basis_in_other_tree(self):
382
        # we should get a InterDirStateTree when
383
        # the source revid is in the dirstate object of the target and
384
        # the dirstates are different. This is largely covered by testing
385
        # with repository revtrees, so is just for extra confidence.
386
        tree = self.make_workingtree('a')
387
        tree.commit('first post')
388
        tree2 = self.make_workingtree('b')
389
        tree2.pull(tree.branch)
390
        basis_tree = tree.basis_tree()
391
        tree2.lock_read()
392
        basis_tree.lock_read()
393
        optimiser = InterTree.get(basis_tree, tree2)
394
        tree2.unlock()
395
        basis_tree.unlock()
396
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
397
398
    def test_merged_revtree_to_tree(self):
399
        # we should get a InterDirStateTree when
400
        # the source tree is a merged tree present in the dirstate of target.
401
        tree = self.make_workingtree('a')
402
        tree.commit('first post')
403
        tree.commit('tree 1 commit 2')
404
        tree2 = self.make_workingtree('b')
405
        tree2.pull(tree.branch)
406
        tree2.commit('tree 2 commit 2')
407
        tree.merge_from_branch(tree2.branch)
408
        second_parent_tree = tree.revision_tree(tree.get_parent_ids()[1])
409
        second_parent_tree.lock_read()
410
        tree.lock_read()
411
        optimiser = InterTree.get(second_parent_tree, tree)
412
        tree.unlock()
413
        second_parent_tree.unlock()
414
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
415
416
    def test_id2path(self):
417
        tree = self.make_workingtree('tree')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
418
        self.build_tree(['tree/a', 'tree/b'])
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
419
        tree.add(['a'], ['a-id'])
420
        self.assertEqual(u'a', tree.id2path('a-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
421
        self.assertRaises(errors.NoSuchId, tree.id2path, 'a')
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
422
        tree.commit('a')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
423
        tree.add(['b'], ['b-id'])
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
424
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
425
        try:
2825.6.1 by Robert Collins
* ``WorkingTree.rename_one`` will now raise an error if normalisation of the
426
            new_path = u'b\u03bcrry'
427
            tree.rename_one('a', new_path)
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
428
        except UnicodeEncodeError:
429
            # support running the test on non-unicode platforms
430
            new_path = 'c'
2825.6.1 by Robert Collins
* ``WorkingTree.rename_one`` will now raise an error if normalisation of the
431
            tree.rename_one('a', new_path)
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
432
        self.assertEqual(new_path, tree.id2path('a-id'))
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
433
        tree.commit(u'b\xb5rry')
434
        tree.unversion(['a-id'])
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
435
        self.assertRaises(errors.NoSuchId, tree.id2path, 'a-id')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
436
        self.assertEqual('b', tree.id2path('b-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
437
        self.assertRaises(errors.NoSuchId, tree.id2path, 'c-id')
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
438
439
    def test_unique_root_id_per_tree(self):
440
        # each time you initialize a new tree, it gets a different root id
2255.2.207 by Robert Collins
Reinstate format change for test_workingtree_4
441
        format_name = 'dirstate-with-subtree'
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
442
        tree1 = self.make_branch_and_tree('tree1',
443
            format=format_name)
444
        tree2 = self.make_branch_and_tree('tree2',
445
            format=format_name)
446
        self.assertNotEqual(tree1.get_root_id(), tree2.get_root_id())
447
        # when you branch, it inherits the same root id
448
        rev1 = tree1.commit('first post')
449
        tree3 = tree1.bzrdir.sprout('tree3').open_workingtree()
450
        self.assertEqual(tree3.get_root_id(), tree1.get_root_id())
451
2255.11.2 by Martin Pool
Add more dirstate root-id-changing tests
452
    def test_set_root_id(self):
453
        # similar to some code that fails in the dirstate-plus-subtree branch
454
        # -- setting the root id while adding a parent seems to scramble the
455
        # dirstate invariants. -- mbp 20070303
456
        def validate():
457
            wt.lock_read()
458
            try:
459
                wt.current_dirstate()._validate()
460
            finally:
461
                wt.unlock()
462
        wt = self.make_workingtree('tree')
463
        wt.set_root_id('TREE-ROOTID')
464
        validate()
465
        wt.commit('somenthing')
466
        validate()
467
        # now switch and commit again
468
        wt.set_root_id('tree-rootid')
469
        validate()
470
        wt.commit('again')
471
        validate()
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
472
1551.15.6 by Aaron Bentley
Use ROOT_ID when the repository supports old clients (Bug #107168)
473
    def test_default_root_id(self):
474
        tree = self.make_branch_and_tree('tag', format='dirstate-tags')
475
        self.assertEqual(inventory.ROOT_ID, tree.get_root_id())
476
        tree = self.make_branch_and_tree('subtree',
477
                                         format='dirstate-with-subtree')
478
        self.assertNotEqual(inventory.ROOT_ID, tree.get_root_id())
479
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
480
    def test_non_subtree_with_nested_trees(self):
481
        # prior to dirstate, st/diff/commit ignored nested trees.
482
        # dirstate, as opposed to dirstate-with-subtree, should
483
        # behave the same way.
484
        tree = self.make_branch_and_tree('.', format='dirstate')
485
        self.assertFalse(tree.supports_tree_reference())
486
        self.build_tree(['dir/'])
487
        # for testing easily.
488
        tree.set_root_id('root')
489
        tree.add(['dir'], ['dir-id'])
490
        subtree = self.make_branch_and_tree('dir')
491
        # the most primitive operation: kind
492
        self.assertEqual('directory', tree.kind('dir-id'))
4570.2.8 by Robert Collins
Adjust WorkingTree4 specific test to deal with iter_changes reporting required directories.
493
        # a diff against the basis should give us a directory and the root (as
494
        # the root is new too).
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
495
        tree.lock_read()
496
        expected = [('dir-id',
497
            (None, u'dir'),
498
            True,
499
            (False, True),
500
            (None, 'root'),
501
            (None, u'dir'),
502
            (None, 'directory'),
4570.2.8 by Robert Collins
Adjust WorkingTree4 specific test to deal with iter_changes reporting required directories.
503
            (None, False)),
504
            ('root', (None, u''), True, (False, True), (None, None),
505
            (None, u''), (None, 'directory'), (None, 0))]
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
506
        self.assertEqual(expected, list(tree.iter_changes(tree.basis_tree(),
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
507
            specific_files=['dir'])))
508
        tree.unlock()
509
        # do a commit, we want to trigger the dirstate fast-path too
510
        tree.commit('first post')
511
        # change the path for the subdir, which will trigger getting all
512
        # its data:
513
        os.rename('dir', 'also-dir')
514
        # now the diff will use the fast path
515
        tree.lock_read()
516
        expected = [('dir-id',
517
            (u'dir', u'dir'),
518
            True,
519
            (True, True),
520
            ('root', 'root'),
521
            ('dir', 'dir'),
522
            ('directory', None),
523
            (False, False))]
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
524
        self.assertEqual(expected, list(tree.iter_changes(tree.basis_tree())))
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
525
        tree.unlock()
526
527
    def test_with_subtree_supports_tree_references(self):
528
        # dirstate-with-subtree should support tree-references.
529
        tree = self.make_branch_and_tree('.', format='dirstate-with-subtree')
530
        self.assertTrue(tree.supports_tree_reference())
531
        # having checked this is on, the tree interface, and intertree
532
        # interface tests, will proceed to test the subtree support of
533
        # workingtree_4.
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
534
535
    def test_iter_changes_ignores_unversioned_dirs(self):
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
536
        """iter_changes should not descend into unversioned directories."""
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
537
        tree = self.make_branch_and_tree('.', format='dirstate')
538
        # We have an unversioned directory at the root, a versioned one with
539
        # other versioned files and an unversioned directory, and another
540
        # versioned dir with nothing but an unversioned directory.
541
        self.build_tree(['unversioned/',
542
                         'unversioned/a',
543
                         'unversioned/b/',
544
                         'versioned/',
545
                         'versioned/unversioned/',
546
                         'versioned/unversioned/a',
547
                         'versioned/unversioned/b/',
548
                         'versioned2/',
549
                         'versioned2/a',
550
                         'versioned2/unversioned/',
551
                         'versioned2/unversioned/a',
552
                         'versioned2/unversioned/b/',
553
                        ])
554
        tree.add(['versioned', 'versioned2', 'versioned2/a'])
555
        tree.commit('one', rev_id='rev-1')
556
        # Trap osutils._walkdirs_utf8 to spy on what dirs have been accessed.
557
        returned = []
558
        orig_walkdirs = osutils._walkdirs_utf8
559
        def reset():
560
            osutils._walkdirs_utf8 = orig_walkdirs
561
        self.addCleanup(reset)
562
        def walkdirs_spy(*args, **kwargs):
563
            for val in orig_walkdirs(*args, **kwargs):
564
                returned.append(val[0][0])
565
                yield val
566
        osutils._walkdirs_utf8 = walkdirs_spy
567
568
        basis = tree.basis_tree()
569
        tree.lock_read()
570
        self.addCleanup(tree.unlock)
571
        basis.lock_read()
572
        self.addCleanup(basis.unlock)
2466.4.2 by John Arbash Meinel
Clean up the (failing) test so that the last thing
573
        changes = [c[1] for c in
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
574
                   tree.iter_changes(basis, want_unversioned=True)]
2466.4.2 by John Arbash Meinel
Clean up the (failing) test so that the last thing
575
        self.assertEqual([(None, 'unversioned'),
576
                          (None, 'versioned/unversioned'),
577
                          (None, 'versioned2/unversioned'),
578
                         ], changes)
579
        self.assertEqual(['', 'versioned', 'versioned2'], returned)
580
        del returned[:] # reset
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
581
        changes = [c[1] for c in tree.iter_changes(basis)]
2466.4.2 by John Arbash Meinel
Clean up the (failing) test so that the last thing
582
        self.assertEqual([], changes)
583
        self.assertEqual(['', 'versioned', 'versioned2'], returned)
3207.2.1 by jameinel
Add a test that _iter_changes raises a clearer error when we encounter an invalid rename.
584
4496.2.1 by Ian Clatworthy
(igc) Improve paths are not versioned reporting (Benoît PIERRE)
585
    def test_iter_changes_unversioned_error(self):
586
        """ Check if a PathsNotVersionedError is correctly raised and the
587
            paths list contains all unversioned entries only.
588
        """
589
        tree = self.make_branch_and_tree('tree')
590
        self.build_tree_contents([('tree/bar', '')])
591
        tree.add(['bar'], ['bar-id'])
592
        tree.lock_read()
593
        self.addCleanup(tree.unlock)
594
        tree_iter_changes = lambda files: [
595
            c for c in tree.iter_changes(tree.basis_tree(), specific_files=files,
596
                                         require_versioned=True)
597
        ]
598
        e = self.assertRaises(errors.PathsNotVersionedError,
599
                              tree_iter_changes, ['bar', 'foo'])
600
        self.assertEqual(e.paths, ['foo'])
601
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
602
    def get_tree_with_cachable_file_foo(self):
603
        tree = self.make_branch_and_tree('.')
604
        self.build_tree(['foo'])
605
        tree.add(['foo'], ['foo-id'])
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
606
        # a 4 second old timestamp is always hashable - sucks to delay
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
607
        # the test suite, but not testing this is worse.
608
        time.sleep(4)
3207.2.1 by jameinel
Add a test that _iter_changes raises a clearer error when we encounter an invalid rename.
609
        return tree
610
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
611
    def test_commit_updates_hash_cache(self):
612
        tree = self.get_tree_with_cachable_file_foo()
613
        revid = tree.commit('a commit')
614
        # tree's dirstate should now have a valid stat entry for foo.
615
        tree.lock_read()
4100.2.3 by Aaron Bentley
Avoid autodetecting tree references in _comparison_data.
616
        self.addCleanup(tree.unlock)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
617
        entry = tree._get_entry(path='foo')
618
        expected_sha1 = osutils.sha_file_by_name('foo')
619
        self.assertEqual(expected_sha1, entry[1][0][1])
620
621
    def test_observed_sha1_cachable(self):
622
        tree = self.get_tree_with_cachable_file_foo()
623
        expected_sha1 = osutils.sha_file_by_name('foo')
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
624
        statvalue = os.lstat("foo")
3207.2.1 by jameinel
Add a test that _iter_changes raises a clearer error when we encounter an invalid rename.
625
        tree.lock_write()
3207.2.2 by John Arbash Meinel
Fix bug #187169, when an invalid delta is supplied to update_basis_by_delta
626
        try:
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
627
            tree._observed_sha1("foo-id", "foo", (expected_sha1, statvalue))
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
628
            self.assertEqual(expected_sha1,
629
                tree._get_entry(path="foo")[1][0][1])
3207.2.2 by John Arbash Meinel
Fix bug #187169, when an invalid delta is supplied to update_basis_by_delta
630
        finally:
631
            tree.unlock()
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
632
        tree = tree.bzrdir.open_workingtree()
633
        tree.lock_read()
3207.2.1 by jameinel
Add a test that _iter_changes raises a clearer error when we encounter an invalid rename.
634
        self.addCleanup(tree.unlock)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
635
        self.assertEqual(expected_sha1, tree._get_entry(path="foo")[1][0][1])
636
637
    def test_observed_sha1_new_file(self):
638
        tree = self.make_branch_and_tree('.')
639
        self.build_tree(['foo'])
640
        tree.add(['foo'], ['foo-id'])
3207.2.2 by John Arbash Meinel
Fix bug #187169, when an invalid delta is supplied to update_basis_by_delta
641
        tree.lock_read()
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
642
        try:
643
            current_sha1 = tree._get_entry(path="foo")[1][0][1]
644
        finally:
645
            tree.unlock()
646
        tree.lock_write()
647
        try:
648
            tree._observed_sha1("foo-id", "foo",
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
649
                (osutils.sha_file_by_name('foo'), os.lstat("foo")))
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
650
            # Must not have changed
651
            self.assertEqual(current_sha1,
652
                tree._get_entry(path="foo")[1][0][1])
653
        finally:
654
            tree.unlock()
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
655
656
    def test_get_file_with_stat_id_only(self):
657
        # Explicit test to ensure we get a lstat value from WT4 trees.
658
        tree = self.make_branch_and_tree('.')
659
        self.build_tree(['foo'])
660
        tree.add(['foo'], ['foo-id'])
661
        tree.lock_read()
662
        self.addCleanup(tree.unlock)
663
        file_obj, statvalue = tree.get_file_with_stat('foo-id')
664
        expected = os.lstat('foo')
665
        self.assertEqualStat(expected, statvalue)
666
        self.assertEqual(["contents of foo\n"], file_obj.readlines())
667
668
669
class TestCorruptDirstate(TestCaseWithTransport):
670
    """Tests for how we handle when the dirstate has been corrupted."""
671
672
    def create_wt4(self):
673
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
674
        control.create_repository()
675
        control.create_branch()
676
        tree = workingtree_4.WorkingTreeFormat4().initialize(control)
677
        return tree
678
679
    def test_invalid_rename(self):
680
        tree = self.create_wt4()
681
        # Create a corrupted dirstate
682
        tree.lock_write()
683
        try:
4285.2.1 by Vincent Ladeuil
Cleanup test imports and use features to better track skipped tests.
684
            # We need a parent, or we always compare with NULL
685
            tree.commit('init')
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
686
            state = tree.current_dirstate()
687
            state._read_dirblocks_if_needed()
688
            # Now add in an invalid entry, a rename with a dangling pointer
689
            state._dirblocks[1][1].append((('', 'foo', 'foo-id'),
690
                                            [('f', '', 0, False, ''),
691
                                             ('r', 'bar', 0 , False, '')]))
692
            self.assertListRaises(errors.CorruptDirstate,
693
                                  tree.iter_changes, tree.basis_tree())
694
        finally:
695
            tree.unlock()
696
697
    def get_simple_dirblocks(self, state):
698
        """Extract the simple information from the DirState.
699
700
        This returns the dirblocks, only with the sha1sum and stat details
701
        filtered out.
702
        """
703
        simple_blocks = []
704
        for block in state._dirblocks:
705
            simple_block = (block[0], [])
706
            for entry in block[1]:
707
                # Include the key for each entry, and for each parent include
708
                # just the minikind, so we know if it was
709
                # present/absent/renamed/etc
710
                simple_block[1].append((entry[0], [i[0] for i in entry[1]]))
711
            simple_blocks.append(simple_block)
712
        return simple_blocks
713
714
    def test_update_basis_with_invalid_delta(self):
715
        """When given an invalid delta, it should abort, and not be saved."""
716
        self.build_tree(['dir/', 'dir/file'])
717
        tree = self.create_wt4()
718
        tree.lock_write()
719
        self.addCleanup(tree.unlock)
720
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
721
        first_revision_id = tree.commit('init')
722
723
        root_id = tree.path2id('')
724
        state = tree.current_dirstate()
725
        state._read_dirblocks_if_needed()
726
        self.assertEqual([
727
            ('', [(('', '', root_id), ['d', 'd'])]),
728
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
729
            ('dir', [(('dir', 'file', 'file-id'), ['f', 'f'])]),
730
        ],  self.get_simple_dirblocks(state))
731
732
        tree.remove(['dir/file'])
733
        self.assertEqual([
734
            ('', [(('', '', root_id), ['d', 'd'])]),
735
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
736
            ('dir', [(('dir', 'file', 'file-id'), ['a', 'f'])]),
737
        ],  self.get_simple_dirblocks(state))
738
        # Make sure the removal is written to disk
739
        tree.flush()
740
741
        # self.assertRaises(Exception, tree.update_basis_by_delta,
742
        new_dir = inventory.InventoryDirectory('dir-id', 'new-dir', root_id)
743
        new_dir.revision = 'new-revision-id'
744
        new_file = inventory.InventoryFile('file-id', 'new-file', root_id)
745
        new_file.revision = 'new-revision-id'
746
        self.assertRaises(errors.InconsistentDelta,
747
            tree.update_basis_by_delta, 'new-revision-id',
748
            [('dir', 'new-dir', 'dir-id', new_dir),
749
             ('dir/file', 'new-dir/new-file', 'file-id', new_file),
750
            ])
751
        del state
752
753
        # Now when we re-read the file it should not have been modified
754
        tree.unlock()
755
        tree.lock_read()
756
        self.assertEqual(first_revision_id, tree.last_revision())
757
        state = tree.current_dirstate()
758
        state._read_dirblocks_if_needed()
759
        self.assertEqual([
760
            ('', [(('', '', root_id), ['d', 'd'])]),
761
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
762
            ('dir', [(('dir', 'file', 'file-id'), ['a', 'f'])]),
763
        ],  self.get_simple_dirblocks(state))