/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
18
"""Tests for WorkingTreeFormat4"""
19
20
from bzrlib import (
21
    bzrdir,
22
    dirstate,
23
    errors,
24
    workingtree_4,
25
    )
26
from bzrlib.lockdir import LockDir
27
from bzrlib.tests import TestCaseWithTransport, TestSkipped
28
from bzrlib.tree import InterTree
29
30
31
class TestWorkingTreeFormat4(TestCaseWithTransport):
32
    """Tests specific to WorkingTreeFormat4."""
33
34
    def test_disk_layout(self):
35
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
36
        control.create_repository()
37
        control.create_branch()
38
        tree = workingtree_4.WorkingTreeFormat4().initialize(control)
39
        # we want:
40
        # format 'Bazaar Working Tree format 4'
41
        # stat-cache = ??
42
        t = control.get_workingtree_transport(None)
43
        self.assertEqualDiff('Bazaar Working Tree format 4\n',
44
                             t.get('format').read())
45
        self.assertFalse(t.has('inventory.basis'))
46
        # no last-revision file means 'None' or 'NULLREVISION'
47
        self.assertFalse(t.has('last-revision'))
48
        # TODO RBC 20060210 do a commit, check the inventory.basis is created 
49
        # correctly and last-revision file becomes present.
50
        # manually make a dirstate toc check the format is as desired.
51
        state = dirstate.DirState.on_file(t.local_abspath('dirstate'))
52
        state.lock_read()
53
        try:
54
            self.assertEqual([], state.get_parent_ids())
55
        finally:
56
            state.unlock()
57
58
    def test_uses_lockdir(self):
59
        """WorkingTreeFormat4 uses its own LockDir:
2255.10.1 by John Arbash Meinel
Update WorkingTree4 so that it doesn't use a HashCache,
60
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
61
            - lock is a directory
62
            - when the WorkingTree is locked, LockDir can see that
63
        """
64
        # this test could be factored into a subclass of tests common to both
65
        # format 3 and 4, but for now its not much of an issue as there is only one in common.
66
        t = self.get_transport()
67
        tree = self.make_workingtree()
68
        self.assertIsDirectory('.bzr', t)
69
        self.assertIsDirectory('.bzr/checkout', t)
70
        self.assertIsDirectory('.bzr/checkout/lock', t)
71
        our_lock = LockDir(t, '.bzr/checkout/lock')
72
        self.assertEquals(our_lock.peek(), None)
73
        tree.lock_write()
74
        self.assertTrue(our_lock.peek())
75
        tree.unlock()
76
        self.assertEquals(our_lock.peek(), None)
77
78
    def make_workingtree(self, relpath=''):
79
        url = self.get_url(relpath)
80
        if relpath:
81
            self.build_tree([relpath + '/'])
82
        dir = bzrdir.BzrDirMetaFormat1().initialize(url)
83
        repo = dir.create_repository()
84
        branch = dir.create_branch()
85
        try:
86
            return workingtree_4.WorkingTreeFormat4().initialize(dir)
87
        except errors.NotLocalUrl:
88
            raise TestSkipped('Not a local URL')
89
90
    # TODO: test that dirstate also stores & retrieves the parent list of 
91
    # workingtree-parent revisions, including when they have multiple parents.
92
    # (in other words, the case when we're constructing a merge of 
93
    # revisions which are themselves merges.)
94
95
    # The simplest case is that the the workingtree's primary 
96
    # parent tree can be retrieved.  This is required for all WorkingTrees, 
97
    # and covered by the generic tests.
98
99
    def test_dirstate_stores_all_parent_inventories(self):
100
        tree = self.make_workingtree()
101
102
        # We're going to build in tree a working tree 
103
        # with three parent trees, with some files in common.  
104
    
105
        # We really don't want to do commit or merge in the new dirstate-based
106
        # tree, because that might not work yet.  So instead we build
107
        # revisions elsewhere and pull them across, doing by hand part of the
108
        # work that merge would do.
109
110
        subtree = self.make_branch_and_tree('subdir')
111
        # writelock the tree so its repository doesn't get readlocked by
112
        # the revision tree locks. This works around the bug where we dont
113
        # permit lock upgrading.
114
        subtree.lock_write()
115
        self.addCleanup(subtree.unlock)
116
        self.build_tree(['subdir/file-a',])
117
        subtree.add(['file-a'], ['id-a'])
118
        rev1 = subtree.commit('commit in subdir')
119
120
        subtree2 = subtree.bzrdir.sprout('subdir2').open_workingtree()
121
        self.build_tree(['subdir2/file-b'])
122
        subtree2.add(['file-b'], ['id-b'])
123
        rev2 = subtree2.commit('commit in subdir2')
124
125
        subtree.flush()
126
        subtree.merge_from_branch(subtree2.branch)
127
        rev3 = subtree.commit('merge from subdir2')
128
129
        repo = tree.branch.repository
130
        repo.fetch(subtree.branch.repository, rev3)
131
        # will also pull the others...
132
133
        # create repository based revision trees
134
        rev1_revtree = subtree.branch.repository.revision_tree(rev1)
135
        rev2_revtree = subtree2.branch.repository.revision_tree(rev2)
136
        rev3_revtree = subtree.branch.repository.revision_tree(rev3)
137
        # tree doesn't contain a text merge yet but we'll just
138
        # set the parents as if a merge had taken place. 
139
        # this should cause the tree data to be folded into the 
140
        # dirstate.
141
        tree.set_parent_trees([
142
            (rev1, rev1_revtree),
143
            (rev2, rev2_revtree),
144
            (rev3, rev3_revtree), ])
145
146
        # create tree-sourced revision trees
147
        rev1_tree = tree.revision_tree(rev1)
148
        rev1_tree.lock_read()
149
        self.addCleanup(rev1_tree.unlock)
150
        rev2_tree = tree.revision_tree(rev2)
151
        rev2_tree.lock_read()
152
        self.addCleanup(rev2_tree.unlock)
153
        rev3_tree = tree.revision_tree(rev3)
154
        rev3_tree.lock_read()
155
        self.addCleanup(rev3_tree.unlock)
156
157
        # now we should be able to get them back out
158
        self.assertTreesEqual(rev1_revtree, rev1_tree)
159
        self.assertTreesEqual(rev2_revtree, rev2_tree)
160
        self.assertTreesEqual(rev3_revtree, rev3_tree)
161
162
    def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
163
        """Setting parent trees on a dirstate working tree takes
164
        the trees it's given and doesn't need to read them from the 
165
        repository.
166
        """
167
        tree = self.make_workingtree()
168
169
        subtree = self.make_branch_and_tree('subdir')
170
        rev1 = subtree.commit('commit in subdir')
171
        rev1_tree = subtree.basis_tree()
172
        rev1_tree.lock_read()
173
        self.addCleanup(rev1_tree.unlock)
174
175
        tree.branch.pull(subtree.branch)
176
177
        # break the repository's legs to make sure it only uses the trees
178
        # it's given; any calls to forbidden methods will raise an 
179
        # AssertionError
180
        repo = tree.branch.repository
181
        repo.get_revision = self.fail
182
        repo.get_inventory = self.fail
183
        repo.get_inventory_xml = self.fail
184
        # try to set the parent trees.
185
        tree.set_parent_trees([(rev1, rev1_tree)])
186
187
    def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
188
        """Getting parent trees from a dirstate tree does not read from the 
189
        repos inventory store. This is an important part of the dirstate
190
        performance optimisation work.
191
        """
192
        tree = self.make_workingtree()
193
194
        subtree = self.make_branch_and_tree('subdir')
195
        # writelock the tree so its repository doesn't get readlocked by
196
        # the revision tree locks. This works around the bug where we dont
197
        # permit lock upgrading.
198
        subtree.lock_write()
199
        self.addCleanup(subtree.unlock)
200
        rev1 = subtree.commit('commit in subdir')
201
        rev1_tree = subtree.basis_tree()
202
        rev1_tree.lock_read()
203
        rev1_tree.inventory
204
        self.addCleanup(rev1_tree.unlock)
205
        rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
206
        rev2_tree = subtree.basis_tree()
207
        rev2_tree.lock_read()
208
        rev2_tree.inventory
209
        self.addCleanup(rev2_tree.unlock)
210
211
        tree.branch.pull(subtree.branch)
212
213
        # break the repository's legs to make sure it only uses the trees
214
        # it's given; any calls to forbidden methods will raise an 
215
        # AssertionError
216
        repo = tree.branch.repository
217
        # dont uncomment this: the revision object must be accessed to 
218
        # answer 'get_parent_ids' for the revision tree- dirstate does not 
219
        # cache the parents of a parent tree at this point.
220
        #repo.get_revision = self.fail
221
        repo.get_inventory = self.fail
222
        repo.get_inventory_xml = self.fail
223
        # set the parent trees.
224
        tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
225
        # read the first tree
226
        result_rev1_tree = tree.revision_tree(rev1)
227
        # read the second
228
        result_rev2_tree = tree.revision_tree(rev2)
229
        # compare - there should be no differences between the handed and 
230
        # returned trees
231
        self.assertTreesEqual(rev1_tree, result_rev1_tree)
232
        self.assertTreesEqual(rev2_tree, result_rev2_tree)
233
234
    def test_dirstate_doesnt_cache_non_parent_trees(self):
235
        """Getting parent trees from a dirstate tree does not read from the 
236
        repos inventory store. This is an important part of the dirstate
237
        performance optimisation work.
238
        """
239
        tree = self.make_workingtree()
240
241
        # make a tree that we can try for, which is able to be returned but
242
        # must not be
243
        subtree = self.make_branch_and_tree('subdir')
244
        rev1 = subtree.commit('commit in subdir')
245
        tree.branch.pull(subtree.branch)
246
        # check it fails
247
        self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
248
249
    def test_no_dirstate_outside_lock(self):
250
        # temporary test until the code is mature enough to test from outside.
251
        """Getting a dirstate object fails if there is no lock."""
252
        def lock_and_call_current_dirstate(tree, lock_method):
253
            getattr(tree, lock_method)()
254
            tree.current_dirstate()
255
            tree.unlock()
256
        tree = self.make_workingtree()
257
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
258
        lock_and_call_current_dirstate(tree, 'lock_read')
259
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
260
        lock_and_call_current_dirstate(tree, 'lock_write')
261
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
262
        lock_and_call_current_dirstate(tree, 'lock_tree_write')
263
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
264
265
    def test_new_dirstate_on_new_lock(self):
266
        # until we have detection for when a dirstate can be reused, we
267
        # want to reparse dirstate on every new lock.
268
        known_dirstates = set()
269
        def lock_and_compare_all_current_dirstate(tree, lock_method):
270
            getattr(tree, lock_method)()
271
            state = tree.current_dirstate()
272
            self.assertFalse(state in known_dirstates)
273
            known_dirstates.add(state)
274
            tree.unlock()
275
        tree = self.make_workingtree()
276
        # lock twice with each type to prevent silly per-lock-type bugs.
277
        # each lock and compare looks for a unique state object.
278
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
279
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
280
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
281
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
282
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
283
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
284
2255.2.122 by Robert Collins
Alter intertree implementation tests to let dirstate inter-trees be correctly parameterised.
285
    def test_constructing_invalid_interdirstate_raises(self):
286
        tree = self.make_workingtree()
287
        rev_id = tree.commit('first post')
288
        rev_id2 = tree.commit('second post')
289
        rev_tree = tree.branch.repository.revision_tree(rev_id)
290
        # Exception is not a great thing to raise, but this test is 
291
        # very short, and code is used to sanity check other tests, so 
292
        # a full error object is YAGNI.
293
        self.assertRaises(
294
            Exception, workingtree_4.InterDirStateTree, rev_tree, tree)
295
        self.assertRaises(
296
            Exception, workingtree_4.InterDirStateTree, tree, rev_tree)
297
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
298
    def test_revtree_to_revtree_not_interdirstate(self):
299
        # we should not get a dirstate optimiser for two repository sourced
300
        # revtrees. we can't prove a negative, so we dont do exhaustive tests
301
        # of all formats; though that could be written in the future it doesn't
302
        # seem well worth it.
303
        tree = self.make_workingtree()
304
        rev_id = tree.commit('first post')
305
        rev_id2 = tree.commit('second post')
306
        rev_tree = tree.branch.repository.revision_tree(rev_id)
307
        rev_tree2 = tree.branch.repository.revision_tree(rev_id2)
308
        optimiser = InterTree.get(rev_tree, rev_tree2)
309
        self.assertIsInstance(optimiser, InterTree)
310
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
311
        optimiser = InterTree.get(rev_tree2, rev_tree)
312
        self.assertIsInstance(optimiser, InterTree)
313
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
314
315
    def test_revtree_not_in_dirstate_to_dirstate_not_interdirstate(self):
316
        # we should not get a dirstate optimiser when the revision id for of
317
        # the source is not in the dirstate of the target.
318
        tree = self.make_workingtree()
319
        rev_id = tree.commit('first post')
320
        rev_id2 = tree.commit('second post')
321
        rev_tree = tree.branch.repository.revision_tree(rev_id)
322
        tree.lock_read()
323
        optimiser = InterTree.get(rev_tree, tree)
324
        self.assertIsInstance(optimiser, InterTree)
325
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
326
        optimiser = InterTree.get(tree, rev_tree)
327
        self.assertIsInstance(optimiser, InterTree)
328
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
329
        tree.unlock()
330
331
    def test_empty_basis_to_dirstate_tree(self):
332
        # we should get a InterDirStateTree for doing
333
        # 'changes_from' from the first basis dirstate revision tree to a
334
        # WorkingTree4.
335
        tree = self.make_workingtree()
336
        tree.lock_read()
337
        basis_tree = tree.basis_tree()
338
        basis_tree.lock_read()
339
        optimiser = InterTree.get(basis_tree, tree)
340
        tree.unlock()
341
        basis_tree.unlock()
342
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
343
344
    def test_nonempty_basis_to_dirstate_tree(self):
345
        # we should get a InterDirStateTree for doing
346
        # 'changes_from' from a non-null basis dirstate revision tree to a
347
        # WorkingTree4.
348
        tree = self.make_workingtree()
349
        tree.commit('first post')
350
        tree.lock_read()
351
        basis_tree = tree.basis_tree()
352
        basis_tree.lock_read()
353
        optimiser = InterTree.get(basis_tree, tree)
354
        tree.unlock()
355
        basis_tree.unlock()
356
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
357
358
    def test_empty_basis_revtree_to_dirstate_tree(self):
359
        # we should get a InterDirStateTree for doing
360
        # 'changes_from' from an empty repository based rev tree to a
361
        # WorkingTree4.
362
        tree = self.make_workingtree()
363
        tree.lock_read()
364
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
365
        basis_tree.lock_read()
366
        optimiser = InterTree.get(basis_tree, tree)
367
        tree.unlock()
368
        basis_tree.unlock()
369
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
370
371
    def test_nonempty_basis_revtree_to_dirstate_tree(self):
372
        # we should get a InterDirStateTree for doing
373
        # 'changes_from' from a non-null repository based rev tree to a
374
        # WorkingTree4.
375
        tree = self.make_workingtree()
376
        tree.commit('first post')
377
        tree.lock_read()
378
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
379
        basis_tree.lock_read()
380
        optimiser = InterTree.get(basis_tree, tree)
381
        tree.unlock()
382
        basis_tree.unlock()
383
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
384
385
    def test_tree_to_basis_in_other_tree(self):
386
        # we should get a InterDirStateTree when
387
        # the source revid is in the dirstate object of the target and
388
        # the dirstates are different. This is largely covered by testing
389
        # with repository revtrees, so is just for extra confidence.
390
        tree = self.make_workingtree('a')
391
        tree.commit('first post')
392
        tree2 = self.make_workingtree('b')
393
        tree2.pull(tree.branch)
394
        basis_tree = tree.basis_tree()
395
        tree2.lock_read()
396
        basis_tree.lock_read()
397
        optimiser = InterTree.get(basis_tree, tree2)
398
        tree2.unlock()
399
        basis_tree.unlock()
400
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
401
402
    def test_merged_revtree_to_tree(self):
403
        # we should get a InterDirStateTree when
404
        # the source tree is a merged tree present in the dirstate of target.
405
        tree = self.make_workingtree('a')
406
        tree.commit('first post')
407
        tree.commit('tree 1 commit 2')
408
        tree2 = self.make_workingtree('b')
409
        tree2.pull(tree.branch)
410
        tree2.commit('tree 2 commit 2')
411
        tree.merge_from_branch(tree2.branch)
412
        second_parent_tree = tree.revision_tree(tree.get_parent_ids()[1])
413
        second_parent_tree.lock_read()
414
        tree.lock_read()
415
        optimiser = InterTree.get(second_parent_tree, tree)
416
        tree.unlock()
417
        second_parent_tree.unlock()
418
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
419
420
    def test_id2path(self):
421
        tree = self.make_workingtree('tree')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
422
        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
423
        tree.add(['a'], ['a-id'])
424
        self.assertEqual(u'a', tree.id2path('a-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
425
        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
426
        tree.commit('a')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
427
        tree.add(['b'], ['b-id'])
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
428
429
        tree.rename_one('a', u'b\xb5rry')
430
        self.assertEqual(u'b\xb5rry', tree.id2path('a-id'))
431
        tree.commit(u'b\xb5rry')
432
        tree.unversion(['a-id'])
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
433
        self.assertRaises(errors.NoSuchId, tree.id2path, 'a-id')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
434
        self.assertEqual('b', tree.id2path('b-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
435
        self.assertRaises(errors.NoSuchId, tree.id2path, 'c-id')
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
436
437
    def test_unique_root_id_per_tree(self):
438
        # 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
439
        format_name = 'dirstate-with-subtree'
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
440
        tree1 = self.make_branch_and_tree('tree1',
441
            format=format_name)
442
        tree2 = self.make_branch_and_tree('tree2',
443
            format=format_name)
444
        self.assertNotEqual(tree1.get_root_id(), tree2.get_root_id())
445
        # when you branch, it inherits the same root id
446
        rev1 = tree1.commit('first post')
447
        tree3 = tree1.bzrdir.sprout('tree3').open_workingtree()
448
        self.assertEqual(tree3.get_root_id(), tree1.get_root_id())
449
2255.11.2 by Martin Pool
Add more dirstate root-id-changing tests
450
    def test_set_root_id(self):
451
        # similar to some code that fails in the dirstate-plus-subtree branch
452
        # -- setting the root id while adding a parent seems to scramble the
453
        # dirstate invariants. -- mbp 20070303
454
        def validate():
455
            wt.lock_read()
456
            try:
457
                wt.current_dirstate()._validate()
458
            finally:
459
                wt.unlock()
460
        wt = self.make_workingtree('tree')
461
        wt.set_root_id('TREE-ROOTID')
462
        validate()
463
        wt.commit('somenthing')
464
        validate()
465
        # now switch and commit again
466
        wt.set_root_id('tree-rootid')
467
        validate()
468
        wt.commit('again')
469
        validate()