/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
2255.2.122 by Robert Collins
Alter intertree implementation tests to let dirstate inter-trees be correctly parameterised.
287
    def test_constructing_invalid_interdirstate_raises(self):
288
        tree = self.make_workingtree()
289
        rev_id = tree.commit('first post')
290
        rev_id2 = tree.commit('second post')
291
        rev_tree = tree.branch.repository.revision_tree(rev_id)
292
        # Exception is not a great thing to raise, but this test is 
293
        # very short, and code is used to sanity check other tests, so 
294
        # a full error object is YAGNI.
295
        self.assertRaises(
296
            Exception, workingtree_4.InterDirStateTree, rev_tree, tree)
297
        self.assertRaises(
298
            Exception, workingtree_4.InterDirStateTree, tree, rev_tree)
299
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
300
    def test_revtree_to_revtree_not_interdirstate(self):
301
        # we should not get a dirstate optimiser for two repository sourced
302
        # revtrees. we can't prove a negative, so we dont do exhaustive tests
303
        # of all formats; though that could be written in the future it doesn't
304
        # seem well worth it.
305
        tree = self.make_workingtree()
306
        rev_id = tree.commit('first post')
307
        rev_id2 = tree.commit('second post')
308
        rev_tree = tree.branch.repository.revision_tree(rev_id)
309
        rev_tree2 = tree.branch.repository.revision_tree(rev_id2)
310
        optimiser = InterTree.get(rev_tree, rev_tree2)
311
        self.assertIsInstance(optimiser, InterTree)
312
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
313
        optimiser = InterTree.get(rev_tree2, rev_tree)
314
        self.assertIsInstance(optimiser, InterTree)
315
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
316
317
    def test_revtree_not_in_dirstate_to_dirstate_not_interdirstate(self):
318
        # we should not get a dirstate optimiser when the revision id for of
319
        # the source is not in the dirstate of the target.
320
        tree = self.make_workingtree()
321
        rev_id = tree.commit('first post')
322
        rev_id2 = tree.commit('second post')
323
        rev_tree = tree.branch.repository.revision_tree(rev_id)
324
        tree.lock_read()
325
        optimiser = InterTree.get(rev_tree, tree)
326
        self.assertIsInstance(optimiser, InterTree)
327
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
328
        optimiser = InterTree.get(tree, rev_tree)
329
        self.assertIsInstance(optimiser, InterTree)
330
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
331
        tree.unlock()
332
333
    def test_empty_basis_to_dirstate_tree(self):
334
        # we should get a InterDirStateTree for doing
335
        # 'changes_from' from the first basis dirstate revision tree to a
336
        # WorkingTree4.
337
        tree = self.make_workingtree()
338
        tree.lock_read()
339
        basis_tree = tree.basis_tree()
340
        basis_tree.lock_read()
341
        optimiser = InterTree.get(basis_tree, tree)
342
        tree.unlock()
343
        basis_tree.unlock()
344
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
345
346
    def test_nonempty_basis_to_dirstate_tree(self):
347
        # we should get a InterDirStateTree for doing
348
        # 'changes_from' from a non-null basis dirstate revision tree to a
349
        # WorkingTree4.
350
        tree = self.make_workingtree()
351
        tree.commit('first post')
352
        tree.lock_read()
353
        basis_tree = tree.basis_tree()
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_empty_basis_revtree_to_dirstate_tree(self):
361
        # we should get a InterDirStateTree for doing
362
        # 'changes_from' from an empty repository based rev tree to a
363
        # WorkingTree4.
364
        tree = self.make_workingtree()
365
        tree.lock_read()
366
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
367
        basis_tree.lock_read()
368
        optimiser = InterTree.get(basis_tree, tree)
369
        tree.unlock()
370
        basis_tree.unlock()
371
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
372
373
    def test_nonempty_basis_revtree_to_dirstate_tree(self):
374
        # we should get a InterDirStateTree for doing
375
        # 'changes_from' from a non-null repository based rev tree to a
376
        # WorkingTree4.
377
        tree = self.make_workingtree()
378
        tree.commit('first post')
379
        tree.lock_read()
380
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
381
        basis_tree.lock_read()
382
        optimiser = InterTree.get(basis_tree, tree)
383
        tree.unlock()
384
        basis_tree.unlock()
385
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
386
387
    def test_tree_to_basis_in_other_tree(self):
388
        # we should get a InterDirStateTree when
389
        # the source revid is in the dirstate object of the target and
390
        # the dirstates are different. This is largely covered by testing
391
        # with repository revtrees, so is just for extra confidence.
392
        tree = self.make_workingtree('a')
393
        tree.commit('first post')
394
        tree2 = self.make_workingtree('b')
395
        tree2.pull(tree.branch)
396
        basis_tree = tree.basis_tree()
397
        tree2.lock_read()
398
        basis_tree.lock_read()
399
        optimiser = InterTree.get(basis_tree, tree2)
400
        tree2.unlock()
401
        basis_tree.unlock()
402
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
403
404
    def test_merged_revtree_to_tree(self):
405
        # we should get a InterDirStateTree when
406
        # the source tree is a merged tree present in the dirstate of target.
407
        tree = self.make_workingtree('a')
408
        tree.commit('first post')
409
        tree.commit('tree 1 commit 2')
410
        tree2 = self.make_workingtree('b')
411
        tree2.pull(tree.branch)
412
        tree2.commit('tree 2 commit 2')
413
        tree.merge_from_branch(tree2.branch)
414
        second_parent_tree = tree.revision_tree(tree.get_parent_ids()[1])
415
        second_parent_tree.lock_read()
416
        tree.lock_read()
417
        optimiser = InterTree.get(second_parent_tree, tree)
418
        tree.unlock()
419
        second_parent_tree.unlock()
420
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)