/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.assertEqualDiff('### bzr hashcache v5\n',
46
                             t.get('stat-cache').read())
47
        self.assertFalse(t.has('inventory.basis'))
48
        # no last-revision file means 'None' or 'NULLREVISION'
49
        self.assertFalse(t.has('last-revision'))
50
        # TODO RBC 20060210 do a commit, check the inventory.basis is created 
51
        # correctly and last-revision file becomes present.
52
        # manually make a dirstate toc check the format is as desired.
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:
62
            
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
67
        # format 3 and 4, but for now its not much of an issue as there is only one in common.
68
        t = self.get_transport()
69
        tree = self.make_workingtree()
70
        self.assertIsDirectory('.bzr', t)
71
        self.assertIsDirectory('.bzr/checkout', t)
72
        self.assertIsDirectory('.bzr/checkout/lock', t)
73
        our_lock = LockDir(t, '.bzr/checkout/lock')
74
        self.assertEquals(our_lock.peek(), None)
75
        tree.lock_write()
76
        self.assertTrue(our_lock.peek())
77
        tree.unlock()
78
        self.assertEquals(our_lock.peek(), None)
79
80
    def make_workingtree(self, relpath=''):
81
        url = self.get_url(relpath)
82
        if relpath:
83
            self.build_tree([relpath + '/'])
84
        dir = bzrdir.BzrDirMetaFormat1().initialize(url)
85
        repo = dir.create_repository()
86
        branch = dir.create_branch()
87
        try:
88
            return workingtree_4.WorkingTreeFormat4().initialize(dir)
89
        except errors.NotLocalUrl:
90
            raise TestSkipped('Not a local URL')
91
92
    # TODO: test that dirstate also stores & retrieves the parent list of 
93
    # workingtree-parent revisions, including when they have multiple parents.
94
    # (in other words, the case when we're constructing a merge of 
95
    # revisions which are themselves merges.)
96
97
    # The simplest case is that the the workingtree's primary 
98
    # parent tree can be retrieved.  This is required for all WorkingTrees, 
99
    # and covered by the generic tests.
100
101
    def test_dirstate_stores_all_parent_inventories(self):
102
        tree = self.make_workingtree()
103
104
        # We're going to build in tree a working tree 
105
        # with three parent trees, with some files in common.  
106
    
107
        # We really don't want to do commit or merge in the new dirstate-based
108
        # tree, because that might not work yet.  So instead we build
109
        # revisions elsewhere and pull them across, doing by hand part of the
110
        # work that merge would do.
111
112
        subtree = self.make_branch_and_tree('subdir')
113
        # writelock the tree so its repository doesn't get readlocked by
114
        # the revision tree locks. This works around the bug where we dont
115
        # permit lock upgrading.
116
        subtree.lock_write()
117
        self.addCleanup(subtree.unlock)
118
        self.build_tree(['subdir/file-a',])
119
        subtree.add(['file-a'], ['id-a'])
120
        rev1 = subtree.commit('commit in subdir')
121
122
        subtree2 = subtree.bzrdir.sprout('subdir2').open_workingtree()
123
        self.build_tree(['subdir2/file-b'])
124
        subtree2.add(['file-b'], ['id-b'])
125
        rev2 = subtree2.commit('commit in subdir2')
126
127
        subtree.flush()
128
        subtree.merge_from_branch(subtree2.branch)
129
        rev3 = subtree.commit('merge from subdir2')
130
131
        repo = tree.branch.repository
132
        repo.fetch(subtree.branch.repository, rev3)
133
        # will also pull the others...
134
135
        # create repository based revision trees
136
        rev1_revtree = subtree.branch.repository.revision_tree(rev1)
137
        rev2_revtree = subtree2.branch.repository.revision_tree(rev2)
138
        rev3_revtree = subtree.branch.repository.revision_tree(rev3)
139
        # tree doesn't contain a text merge yet but we'll just
140
        # set the parents as if a merge had taken place. 
141
        # this should cause the tree data to be folded into the 
142
        # dirstate.
143
        tree.set_parent_trees([
144
            (rev1, rev1_revtree),
145
            (rev2, rev2_revtree),
146
            (rev3, rev3_revtree), ])
147
148
        # create tree-sourced revision trees
149
        rev1_tree = tree.revision_tree(rev1)
150
        rev1_tree.lock_read()
151
        self.addCleanup(rev1_tree.unlock)
152
        rev2_tree = tree.revision_tree(rev2)
153
        rev2_tree.lock_read()
154
        self.addCleanup(rev2_tree.unlock)
155
        rev3_tree = tree.revision_tree(rev3)
156
        rev3_tree.lock_read()
157
        self.addCleanup(rev3_tree.unlock)
158
159
        # now we should be able to get them back out
160
        self.assertTreesEqual(rev1_revtree, rev1_tree)
161
        self.assertTreesEqual(rev2_revtree, rev2_tree)
162
        self.assertTreesEqual(rev3_revtree, rev3_tree)
163
164
    def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
165
        """Setting parent trees on a dirstate working tree takes
166
        the trees it's given and doesn't need to read them from the 
167
        repository.
168
        """
169
        tree = self.make_workingtree()
170
171
        subtree = self.make_branch_and_tree('subdir')
172
        rev1 = subtree.commit('commit in subdir')
173
        rev1_tree = subtree.basis_tree()
174
        rev1_tree.lock_read()
175
        self.addCleanup(rev1_tree.unlock)
176
177
        tree.branch.pull(subtree.branch)
178
179
        # break the repository's legs to make sure it only uses the trees
180
        # it's given; any calls to forbidden methods will raise an 
181
        # AssertionError
182
        repo = tree.branch.repository
183
        repo.get_revision = self.fail
184
        repo.get_inventory = self.fail
185
        repo.get_inventory_xml = self.fail
186
        # try to set the parent trees.
187
        tree.set_parent_trees([(rev1, rev1_tree)])
188
189
    def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
190
        """Getting parent trees from a dirstate tree does not read from the 
191
        repos inventory store. This is an important part of the dirstate
192
        performance optimisation work.
193
        """
194
        tree = self.make_workingtree()
195
196
        subtree = self.make_branch_and_tree('subdir')
197
        # writelock the tree so its repository doesn't get readlocked by
198
        # the revision tree locks. This works around the bug where we dont
199
        # permit lock upgrading.
200
        subtree.lock_write()
201
        self.addCleanup(subtree.unlock)
202
        rev1 = subtree.commit('commit in subdir')
203
        rev1_tree = subtree.basis_tree()
204
        rev1_tree.lock_read()
205
        rev1_tree.inventory
206
        self.addCleanup(rev1_tree.unlock)
207
        rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
208
        rev2_tree = subtree.basis_tree()
209
        rev2_tree.lock_read()
210
        rev2_tree.inventory
211
        self.addCleanup(rev2_tree.unlock)
212
213
        tree.branch.pull(subtree.branch)
214
215
        # break the repository's legs to make sure it only uses the trees
216
        # it's given; any calls to forbidden methods will raise an 
217
        # AssertionError
218
        repo = tree.branch.repository
219
        # dont uncomment this: the revision object must be accessed to 
220
        # answer 'get_parent_ids' for the revision tree- dirstate does not 
221
        # cache the parents of a parent tree at this point.
222
        #repo.get_revision = self.fail
223
        repo.get_inventory = self.fail
224
        repo.get_inventory_xml = self.fail
225
        # set the parent trees.
226
        tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
227
        # read the first tree
228
        result_rev1_tree = tree.revision_tree(rev1)
229
        # read the second
230
        result_rev2_tree = tree.revision_tree(rev2)
231
        # compare - there should be no differences between the handed and 
232
        # returned trees
233
        self.assertTreesEqual(rev1_tree, result_rev1_tree)
234
        self.assertTreesEqual(rev2_tree, result_rev2_tree)
235
236
    def test_dirstate_doesnt_cache_non_parent_trees(self):
237
        """Getting parent trees from a dirstate tree does not read from the 
238
        repos inventory store. This is an important part of the dirstate
239
        performance optimisation work.
240
        """
241
        tree = self.make_workingtree()
242
243
        # make a tree that we can try for, which is able to be returned but
244
        # must not be
245
        subtree = self.make_branch_and_tree('subdir')
246
        rev1 = subtree.commit('commit in subdir')
247
        tree.branch.pull(subtree.branch)
248
        # check it fails
249
        self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
250
251
    def test_no_dirstate_outside_lock(self):
252
        # temporary test until the code is mature enough to test from outside.
253
        """Getting a dirstate object fails if there is no lock."""
254
        def lock_and_call_current_dirstate(tree, lock_method):
255
            getattr(tree, lock_method)()
256
            tree.current_dirstate()
257
            tree.unlock()
258
        tree = self.make_workingtree()
259
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
260
        lock_and_call_current_dirstate(tree, 'lock_read')
261
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
262
        lock_and_call_current_dirstate(tree, 'lock_write')
263
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
264
        lock_and_call_current_dirstate(tree, 'lock_tree_write')
265
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
266
267
    def test_new_dirstate_on_new_lock(self):
268
        # until we have detection for when a dirstate can be reused, we
269
        # want to reparse dirstate on every new lock.
270
        known_dirstates = set()
271
        def lock_and_compare_all_current_dirstate(tree, lock_method):
272
            getattr(tree, lock_method)()
273
            state = tree.current_dirstate()
274
            self.assertFalse(state in known_dirstates)
275
            known_dirstates.add(state)
276
            tree.unlock()
277
        tree = self.make_workingtree()
278
        # lock twice with each type to prevent silly per-lock-type bugs.
279
        # each lock and compare looks for a unique state object.
280
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
281
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
282
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
283
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
284
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
285
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
286
287
    def test_revtree_to_revtree_not_interdirstate(self):
288
        # we should not get a dirstate optimiser for two repository sourced
289
        # revtrees. we can't prove a negative, so we dont do exhaustive tests
290
        # of all formats; though that could be written in the future it doesn't
291
        # seem well worth it.
292
        tree = self.make_workingtree()
293
        rev_id = tree.commit('first post')
294
        rev_id2 = tree.commit('second post')
295
        rev_tree = tree.branch.repository.revision_tree(rev_id)
296
        rev_tree2 = tree.branch.repository.revision_tree(rev_id2)
297
        optimiser = InterTree.get(rev_tree, rev_tree2)
298
        self.assertIsInstance(optimiser, InterTree)
299
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
300
        optimiser = InterTree.get(rev_tree2, rev_tree)
301
        self.assertIsInstance(optimiser, InterTree)
302
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
303
304
    def test_revtree_not_in_dirstate_to_dirstate_not_interdirstate(self):
305
        # we should not get a dirstate optimiser when the revision id for of
306
        # the source is not in the dirstate of the target.
307
        tree = self.make_workingtree()
308
        rev_id = tree.commit('first post')
309
        rev_id2 = tree.commit('second post')
310
        rev_tree = tree.branch.repository.revision_tree(rev_id)
311
        tree.lock_read()
312
        optimiser = InterTree.get(rev_tree, tree)
313
        self.assertIsInstance(optimiser, InterTree)
314
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
315
        optimiser = InterTree.get(tree, rev_tree)
316
        self.assertIsInstance(optimiser, InterTree)
317
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
318
        tree.unlock()
319
320
    def test_empty_basis_to_dirstate_tree(self):
321
        # we should get a InterDirStateTree for doing
322
        # 'changes_from' from the first basis dirstate revision tree to a
323
        # WorkingTree4.
324
        tree = self.make_workingtree()
325
        tree.lock_read()
326
        basis_tree = tree.basis_tree()
327
        basis_tree.lock_read()
328
        optimiser = InterTree.get(basis_tree, tree)
329
        tree.unlock()
330
        basis_tree.unlock()
331
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
332
333
    def test_nonempty_basis_to_dirstate_tree(self):
334
        # we should get a InterDirStateTree for doing
335
        # 'changes_from' from a non-null basis dirstate revision tree to a
336
        # WorkingTree4.
337
        tree = self.make_workingtree()
338
        tree.commit('first post')
339
        tree.lock_read()
340
        basis_tree = tree.basis_tree()
341
        basis_tree.lock_read()
342
        optimiser = InterTree.get(basis_tree, tree)
343
        tree.unlock()
344
        basis_tree.unlock()
345
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
346
347
    def test_empty_basis_revtree_to_dirstate_tree(self):
348
        # we should get a InterDirStateTree for doing
349
        # 'changes_from' from an empty repository based rev tree to a
350
        # WorkingTree4.
351
        tree = self.make_workingtree()
352
        tree.lock_read()
353
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
354
        basis_tree.lock_read()
355
        optimiser = InterTree.get(basis_tree, tree)
356
        tree.unlock()
357
        basis_tree.unlock()
358
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
359
360
    def test_nonempty_basis_revtree_to_dirstate_tree(self):
361
        # we should get a InterDirStateTree for doing
362
        # 'changes_from' from a non-null repository based rev tree to a
363
        # WorkingTree4.
364
        tree = self.make_workingtree()
365
        tree.commit('first post')
366
        tree.lock_read()
367
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
368
        basis_tree.lock_read()
369
        optimiser = InterTree.get(basis_tree, tree)
370
        tree.unlock()
371
        basis_tree.unlock()
372
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
373
374
    def test_tree_to_basis_in_other_tree(self):
375
        # we should get a InterDirStateTree when
376
        # the source revid is in the dirstate object of the target and
377
        # the dirstates are different. This is largely covered by testing
378
        # with repository revtrees, so is just for extra confidence.
379
        tree = self.make_workingtree('a')
380
        tree.commit('first post')
381
        tree2 = self.make_workingtree('b')
382
        tree2.pull(tree.branch)
383
        basis_tree = tree.basis_tree()
384
        tree2.lock_read()
385
        basis_tree.lock_read()
386
        optimiser = InterTree.get(basis_tree, tree2)
387
        tree2.unlock()
388
        basis_tree.unlock()
389
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
390
391
    def test_merged_revtree_to_tree(self):
392
        # we should get a InterDirStateTree when
393
        # the source tree is a merged tree present in the dirstate of target.
394
        tree = self.make_workingtree('a')
395
        tree.commit('first post')
396
        tree.commit('tree 1 commit 2')
397
        tree2 = self.make_workingtree('b')
398
        tree2.pull(tree.branch)
399
        tree2.commit('tree 2 commit 2')
400
        tree.merge_from_branch(tree2.branch)
401
        second_parent_tree = tree.revision_tree(tree.get_parent_ids()[1])
402
        second_parent_tree.lock_read()
403
        tree.lock_read()
404
        optimiser = InterTree.get(second_parent_tree, tree)
405
        tree.unlock()
406
        second_parent_tree.unlock()
407
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)