~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_workingtree_4.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2012, 2016 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
# Authors:  Robert Collins <robert.collins@canonical.com>
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
13
13
#
14
14
# You should have received a copy of the GNU General Public License
15
15
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
 
18
18
"""Tests for WorkingTreeFormat4"""
19
19
 
20
20
import os
21
 
import time
22
21
 
23
22
from bzrlib import (
24
23
    bzrdir,
29
28
    workingtree_4,
30
29
    )
31
30
from bzrlib.lockdir import LockDir
32
 
from bzrlib.tests import TestCaseWithTransport, TestSkipped, features
 
31
from bzrlib.tests import TestCaseWithTransport, TestSkipped
33
32
from bzrlib.tree import InterTree
34
33
 
35
34
 
57
56
        finally:
58
57
            state.unlock()
59
58
 
60
 
    def test_resets_ignores_on_last_unlock(self):
61
 
        # Only the last unlock call will actually reset the
62
 
        # ignores. (bug #785671)
63
 
        tree = self.make_workingtree()
64
 
        tree.lock_read()
65
 
        try:
66
 
            tree.lock_read()
67
 
            try:
68
 
                tree.is_ignored("foo")
69
 
            finally:
70
 
                tree.unlock()
71
 
            self.assertIsNot(None, tree._ignoreglobster)
72
 
        finally:
73
 
            tree.unlock()
74
 
        self.assertIs(None, tree._ignoreglobster)
75
 
 
76
59
    def test_uses_lockdir(self):
77
60
        """WorkingTreeFormat4 uses its own LockDir:
78
61
 
80
63
            - when the WorkingTree is locked, LockDir can see that
81
64
        """
82
65
        # this test could be factored into a subclass of tests common to both
83
 
        # format 3 and 4, but for now its not much of an issue as there is only
84
 
        # one in common.
 
66
        # format 3 and 4, but for now its not much of an issue as there is only one in common.
85
67
        t = self.get_transport()
86
68
        tree = self.make_workingtree()
87
69
        self.assertIsDirectory('.bzr', t)
88
70
        self.assertIsDirectory('.bzr/checkout', t)
89
71
        self.assertIsDirectory('.bzr/checkout/lock', t)
90
72
        our_lock = LockDir(t, '.bzr/checkout/lock')
91
 
        self.assertEqual(our_lock.peek(), None)
 
73
        self.assertEquals(our_lock.peek(), None)
92
74
        tree.lock_write()
93
75
        self.assertTrue(our_lock.peek())
94
76
        tree.unlock()
95
 
        self.assertEqual(our_lock.peek(), None)
 
77
        self.assertEquals(our_lock.peek(), None)
96
78
 
97
79
    def make_workingtree(self, relpath=''):
98
80
        url = self.get_url(relpath)
109
91
    def test_dirstate_stores_all_parent_inventories(self):
110
92
        tree = self.make_workingtree()
111
93
 
112
 
        # We're going to build in tree a working tree
113
 
        # with three parent trees, with some files in common.
114
 
 
 
94
        # We're going to build in tree a working tree 
 
95
        # with three parent trees, with some files in common.  
 
96
    
115
97
        # We really don't want to do commit or merge in the new dirstate-based
116
98
        # tree, because that might not work yet.  So instead we build
117
99
        # revisions elsewhere and pull them across, doing by hand part of the
147
129
        rev2_revtree = repo.revision_tree(rev2)
148
130
        rev3_revtree = repo.revision_tree(rev3)
149
131
        # tree doesn't contain a text merge yet but we'll just
150
 
        # set the parents as if a merge had taken place.
151
 
        # this should cause the tree data to be folded into the
 
132
        # set the parents as if a merge had taken place. 
 
133
        # this should cause the tree data to be folded into the 
152
134
        # dirstate.
153
135
        tree.set_parent_trees([
154
136
            (rev1, rev1_revtree),
173
155
 
174
156
    def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
175
157
        """Setting parent trees on a dirstate working tree takes
176
 
        the trees it's given and doesn't need to read them from the
 
158
        the trees it's given and doesn't need to read them from the 
177
159
        repository.
178
160
        """
179
161
        tree = self.make_workingtree()
187
169
        tree.branch.pull(subtree.branch)
188
170
 
189
171
        # break the repository's legs to make sure it only uses the trees
190
 
        # it's given; any calls to forbidden methods will raise an
 
172
        # it's given; any calls to forbidden methods will raise an 
191
173
        # AssertionError
192
174
        repo = tree.branch.repository
193
 
        self.overrideAttr(repo, "get_revision", self.fail)
194
 
        self.overrideAttr(repo, "get_inventory", self.fail)
195
 
        self.overrideAttr(repo, "_get_inventory_xml", self.fail)
 
175
        repo.get_revision = self.fail
 
176
        repo.get_inventory = self.fail
 
177
        repo.get_inventory_xml = self.fail
196
178
        # try to set the parent trees.
197
179
        tree.set_parent_trees([(rev1, rev1_tree)])
198
180
 
199
181
    def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
200
 
        """Getting parent trees from a dirstate tree does not read from the
 
182
        """Getting parent trees from a dirstate tree does not read from the 
201
183
        repos inventory store. This is an important part of the dirstate
202
184
        performance optimisation work.
203
185
        """
212
194
        rev1 = subtree.commit('commit in subdir')
213
195
        rev1_tree = subtree.basis_tree()
214
196
        rev1_tree.lock_read()
215
 
        rev1_tree.root_inventory
 
197
        rev1_tree.inventory
216
198
        self.addCleanup(rev1_tree.unlock)
217
199
        rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
218
200
        rev2_tree = subtree.basis_tree()
219
201
        rev2_tree.lock_read()
220
 
        rev2_tree.root_inventory
 
202
        rev2_tree.inventory
221
203
        self.addCleanup(rev2_tree.unlock)
222
204
 
223
205
        tree.branch.pull(subtree.branch)
224
206
 
225
207
        # break the repository's legs to make sure it only uses the trees
226
 
        # it's given; any calls to forbidden methods will raise an
 
208
        # it's given; any calls to forbidden methods will raise an 
227
209
        # AssertionError
228
210
        repo = tree.branch.repository
229
 
        # dont uncomment this: the revision object must be accessed to
230
 
        # answer 'get_parent_ids' for the revision tree- dirstate does not
 
211
        # dont uncomment this: the revision object must be accessed to 
 
212
        # answer 'get_parent_ids' for the revision tree- dirstate does not 
231
213
        # cache the parents of a parent tree at this point.
232
214
        #repo.get_revision = self.fail
233
 
        self.overrideAttr(repo, "get_inventory", self.fail)
234
 
        self.overrideAttr(repo, "_get_inventory_xml", self.fail)
 
215
        repo.get_inventory = self.fail
 
216
        repo.get_inventory_xml = self.fail
235
217
        # set the parent trees.
236
218
        tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
237
219
        # read the first tree
238
220
        result_rev1_tree = tree.revision_tree(rev1)
239
221
        # read the second
240
222
        result_rev2_tree = tree.revision_tree(rev2)
241
 
        # compare - there should be no differences between the handed and
 
223
        # compare - there should be no differences between the handed and 
242
224
        # returned trees
243
225
        self.assertTreesEqual(rev1_tree, result_rev1_tree)
244
226
        self.assertTreesEqual(rev2_tree, result_rev2_tree)
245
227
 
246
228
    def test_dirstate_doesnt_cache_non_parent_trees(self):
247
 
        """Getting parent trees from a dirstate tree does not read from the
 
229
        """Getting parent trees from a dirstate tree does not read from the 
248
230
        repos inventory store. This is an important part of the dirstate
249
231
        performance optimisation work.
250
232
        """
274
256
        lock_and_call_current_dirstate(tree, 'lock_tree_write')
275
257
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
276
258
 
277
 
    def test_set_parent_trees_uses_update_basis_by_delta(self):
278
 
        builder = self.make_branch_builder('source')
279
 
        builder.start_series()
280
 
        self.addCleanup(builder.finish_series)
281
 
        builder.build_snapshot('A', [], [
282
 
            ('add', ('', 'root-id', 'directory', None)),
283
 
            ('add', ('a', 'a-id', 'file', 'content\n'))])
284
 
        builder.build_snapshot('B', ['A'], [
285
 
            ('modify', ('a-id', 'new content\nfor a\n')),
286
 
            ('add', ('b', 'b-id', 'file', 'b-content\n'))])
287
 
        tree = self.make_workingtree('tree')
288
 
        source_branch = builder.get_branch()
289
 
        tree.branch.repository.fetch(source_branch.repository, 'B')
290
 
        tree.pull(source_branch, stop_revision='A')
291
 
        tree.lock_write()
292
 
        self.addCleanup(tree.unlock)
293
 
        state = tree.current_dirstate()
294
 
        called = []
295
 
        orig_update = state.update_basis_by_delta
296
 
        def log_update_basis_by_delta(delta, new_revid):
297
 
            called.append(new_revid)
298
 
            return orig_update(delta, new_revid)
299
 
        state.update_basis_by_delta = log_update_basis_by_delta
300
 
        basis = tree.basis_tree()
301
 
        self.assertEqual('a-id', basis.path2id('a'))
302
 
        self.assertEqual(None, basis.path2id('b'))
303
 
        def fail_set_parent_trees(trees, ghosts):
304
 
            raise AssertionError('dirstate.set_parent_trees() was called')
305
 
        state.set_parent_trees = fail_set_parent_trees
306
 
        repo = tree.branch.repository
307
 
        tree.pull(source_branch, stop_revision='B')
308
 
        self.assertEqual(['B'], called)
309
 
        basis = tree.basis_tree()
310
 
        self.assertEqual('a-id', basis.path2id('a'))
311
 
        self.assertEqual('b-id', basis.path2id('b'))
312
 
 
313
 
    def test_set_parent_trees_handles_missing_basis(self):
314
 
        builder = self.make_branch_builder('source')
315
 
        builder.start_series()
316
 
        self.addCleanup(builder.finish_series)
317
 
        builder.build_snapshot('A', [], [
318
 
            ('add', ('', 'root-id', 'directory', None)),
319
 
            ('add', ('a', 'a-id', 'file', 'content\n'))])
320
 
        builder.build_snapshot('B', ['A'], [
321
 
            ('modify', ('a-id', 'new content\nfor a\n')),
322
 
            ('add', ('b', 'b-id', 'file', 'b-content\n'))])
323
 
        builder.build_snapshot('C', ['A'], [
324
 
            ('add', ('c', 'c-id', 'file', 'c-content\n'))])
325
 
        b_c = self.make_branch('branch_with_c')
326
 
        b_c.pull(builder.get_branch(), stop_revision='C')
327
 
        b_b = self.make_branch('branch_with_b')
328
 
        b_b.pull(builder.get_branch(), stop_revision='B')
329
 
        # This is reproducing some of what 'switch' does, just to isolate the
330
 
        # set_parent_trees() step.
331
 
        wt = b_b.create_checkout('tree', lightweight=True)
332
 
        fmt = wt.bzrdir.find_branch_format()
333
 
        fmt.set_reference(wt.bzrdir, None, b_c)
334
 
        # Re-open with the new reference
335
 
        wt = wt.bzrdir.open_workingtree()
336
 
        wt.set_parent_trees([('C', b_c.repository.revision_tree('C'))])
337
 
        self.assertEqual(None, wt.basis_tree().path2id('b'))
338
 
 
339
259
    def test_new_dirstate_on_new_lock(self):
340
260
        # until we have detection for when a dirstate can be reused, we
341
261
        # want to reparse dirstate on every new lock.
361
281
        rev_id = tree.commit('first post')
362
282
        rev_id2 = tree.commit('second post')
363
283
        rev_tree = tree.branch.repository.revision_tree(rev_id)
364
 
        # Exception is not a great thing to raise, but this test is
365
 
        # very short, and code is used to sanity check other tests, so
 
284
        # Exception is not a great thing to raise, but this test is 
 
285
        # very short, and code is used to sanity check other tests, so 
366
286
        # a full error object is YAGNI.
367
287
        self.assertRaises(
368
288
            Exception, workingtree_4.InterDirStateTree, rev_tree, tree)
516
436
 
517
437
    def test_unique_root_id_per_tree(self):
518
438
        # each time you initialize a new tree, it gets a different root id
519
 
        format_name = 'development-subtree'
 
439
        format_name = 'dirstate-with-subtree'
520
440
        tree1 = self.make_branch_and_tree('tree1',
521
441
            format=format_name)
522
442
        tree2 = self.make_branch_and_tree('tree2',
552
472
        tree = self.make_branch_and_tree('tag', format='dirstate-tags')
553
473
        self.assertEqual(inventory.ROOT_ID, tree.get_root_id())
554
474
        tree = self.make_branch_and_tree('subtree',
555
 
                                         format='development-subtree')
 
475
                                         format='dirstate-with-subtree')
556
476
        self.assertNotEqual(inventory.ROOT_ID, tree.get_root_id())
557
477
 
558
478
    def test_non_subtree_with_nested_trees(self):
559
479
        # prior to dirstate, st/diff/commit ignored nested trees.
560
 
        # dirstate, as opposed to development-subtree, should
 
480
        # dirstate, as opposed to dirstate-with-subtree, should
561
481
        # behave the same way.
562
482
        tree = self.make_branch_and_tree('.', format='dirstate')
563
483
        self.assertFalse(tree.supports_tree_reference())
568
488
        subtree = self.make_branch_and_tree('dir')
569
489
        # the most primitive operation: kind
570
490
        self.assertEqual('directory', tree.kind('dir-id'))
571
 
        # a diff against the basis should give us a directory and the root (as
572
 
        # the root is new too).
 
491
        # a diff against the basis should give us a directory
573
492
        tree.lock_read()
574
493
        expected = [('dir-id',
575
494
            (None, u'dir'),
578
497
            (None, 'root'),
579
498
            (None, u'dir'),
580
499
            (None, 'directory'),
581
 
            (None, False)),
582
 
            ('root', (None, u''), True, (False, True), (None, None),
583
 
            (None, u''), (None, 'directory'), (None, 0))]
 
500
            (None, False))]
584
501
        self.assertEqual(expected, list(tree.iter_changes(tree.basis_tree(),
585
502
            specific_files=['dir'])))
586
503
        tree.unlock()
603
520
        tree.unlock()
604
521
 
605
522
    def test_with_subtree_supports_tree_references(self):
606
 
        # development-subtree should support tree-references.
607
 
        tree = self.make_branch_and_tree('.', format='development-subtree')
 
523
        # dirstate-with-subtree should support tree-references.
 
524
        tree = self.make_branch_and_tree('.', format='dirstate-with-subtree')
608
525
        self.assertTrue(tree.supports_tree_reference())
609
526
        # having checked this is on, the tree interface, and intertree
610
527
        # interface tests, will proceed to test the subtree support of
633
550
        tree.commit('one', rev_id='rev-1')
634
551
        # Trap osutils._walkdirs_utf8 to spy on what dirs have been accessed.
635
552
        returned = []
 
553
        orig_walkdirs = osutils._walkdirs_utf8
 
554
        def reset():
 
555
            osutils._walkdirs_utf8 = orig_walkdirs
 
556
        self.addCleanup(reset)
636
557
        def walkdirs_spy(*args, **kwargs):
637
 
            for val in orig(*args, **kwargs):
 
558
            for val in orig_walkdirs(*args, **kwargs):
638
559
                returned.append(val[0][0])
639
560
                yield val
640
 
        orig = self.overrideAttr(osutils, '_walkdirs_utf8', walkdirs_spy)
 
561
        osutils._walkdirs_utf8 = walkdirs_spy
641
562
 
642
563
        basis = tree.basis_tree()
643
564
        tree.lock_read()
656
577
        self.assertEqual([], changes)
657
578
        self.assertEqual(['', 'versioned', 'versioned2'], returned)
658
579
 
659
 
    def test_iter_changes_unversioned_error(self):
660
 
        """ Check if a PathsNotVersionedError is correctly raised and the
661
 
            paths list contains all unversioned entries only.
662
 
        """
663
 
        tree = self.make_branch_and_tree('tree')
664
 
        self.build_tree_contents([('tree/bar', '')])
665
 
        tree.add(['bar'], ['bar-id'])
666
 
        tree.lock_read()
667
 
        self.addCleanup(tree.unlock)
668
 
        tree_iter_changes = lambda files: [
669
 
            c for c in tree.iter_changes(tree.basis_tree(), specific_files=files,
670
 
                                         require_versioned=True)
671
 
        ]
672
 
        e = self.assertRaises(errors.PathsNotVersionedError,
673
 
                              tree_iter_changes, ['bar', 'foo'])
674
 
        self.assertEqual(e.paths, ['foo'])
675
 
 
676
 
    def test_iter_changes_unversioned_non_ascii(self):
677
 
        """Unversioned non-ascii paths should be reported as unicode"""
678
 
        self.requireFeature(features.UnicodeFilenameFeature)
679
 
        tree = self.make_branch_and_tree('.')
680
 
        self.build_tree_contents([('f', '')])
681
 
        tree.add(['f'], ['f-id'])
682
 
        def tree_iter_changes(tree, files):
683
 
            return list(tree.iter_changes(tree.basis_tree(),
684
 
                specific_files=files, require_versioned=True))
685
 
        tree.lock_read()
686
 
        self.addCleanup(tree.unlock)
687
 
        e = self.assertRaises(errors.PathsNotVersionedError,
688
 
            tree_iter_changes, tree, [u'\xa7', u'\u03c0'])
689
 
        self.assertEqual(e.paths, [u'\xa7', u'\u03c0'])
690
 
 
691
 
    def get_tree_with_cachable_file_foo(self):
692
 
        tree = self.make_branch_and_tree('.')
693
 
        tree.lock_write()
694
 
        self.addCleanup(tree.unlock)
695
 
        self.build_tree_contents([('foo', 'a bit of content for foo\n')])
696
 
        tree.add(['foo'], ['foo-id'])
697
 
        tree.current_dirstate()._cutoff_time = time.time() + 60
698
 
        return tree
699
 
 
700
 
    def test_commit_updates_hash_cache(self):
701
 
        tree = self.get_tree_with_cachable_file_foo()
702
 
        revid = tree.commit('a commit')
703
 
        # tree's dirstate should now have a valid stat entry for foo.
704
 
        entry = tree._get_entry(path='foo')
705
 
        expected_sha1 = osutils.sha_file_by_name('foo')
706
 
        self.assertEqual(expected_sha1, entry[1][0][1])
707
 
        self.assertEqual(len('a bit of content for foo\n'), entry[1][0][2])
708
 
 
709
 
    def test_observed_sha1_cachable(self):
710
 
        tree = self.get_tree_with_cachable_file_foo()
711
 
        expected_sha1 = osutils.sha_file_by_name('foo')
712
 
        statvalue = os.lstat("foo")
713
 
        tree._observed_sha1("foo-id", "foo", (expected_sha1, statvalue))
714
 
        entry = tree._get_entry(path="foo")
715
 
        entry_state = entry[1][0]
716
 
        self.assertEqual(expected_sha1, entry_state[1])
717
 
        self.assertEqual(statvalue.st_size, entry_state[2])
718
 
        tree.unlock()
719
 
        tree.lock_read()
720
 
        tree = tree.bzrdir.open_workingtree()
721
 
        tree.lock_read()
722
 
        self.addCleanup(tree.unlock)
723
 
        entry = tree._get_entry(path="foo")
724
 
        entry_state = entry[1][0]
725
 
        self.assertEqual(expected_sha1, entry_state[1])
726
 
        self.assertEqual(statvalue.st_size, entry_state[2])
727
 
 
728
 
    def test_observed_sha1_new_file(self):
729
 
        tree = self.make_branch_and_tree('.')
730
 
        self.build_tree(['foo'])
731
 
        tree.add(['foo'], ['foo-id'])
732
 
        tree.lock_read()
733
 
        try:
734
 
            current_sha1 = tree._get_entry(path="foo")[1][0][1]
735
 
        finally:
736
 
            tree.unlock()
737
 
        tree.lock_write()
738
 
        try:
739
 
            tree._observed_sha1("foo-id", "foo",
740
 
                (osutils.sha_file_by_name('foo'), os.lstat("foo")))
741
 
            # Must not have changed
742
 
            self.assertEqual(current_sha1,
743
 
                tree._get_entry(path="foo")[1][0][1])
744
 
        finally:
745
 
            tree.unlock()
746
 
 
747
 
    def test_get_file_with_stat_id_only(self):
748
 
        # Explicit test to ensure we get a lstat value from WT4 trees.
749
 
        tree = self.make_branch_and_tree('.')
750
 
        self.build_tree(['foo'])
751
 
        tree.add(['foo'], ['foo-id'])
752
 
        tree.lock_read()
753
 
        self.addCleanup(tree.unlock)
754
 
        file_obj, statvalue = tree.get_file_with_stat('foo-id')
755
 
        expected = os.lstat('foo')
756
 
        self.assertEqualStat(expected, statvalue)
757
 
        self.assertEqual(["contents of foo\n"], file_obj.readlines())
758
 
 
759
580
 
760
581
class TestCorruptDirstate(TestCaseWithTransport):
761
582
    """Tests for how we handle when the dirstate has been corrupted."""
772
593
        # Create a corrupted dirstate
773
594
        tree.lock_write()
774
595
        try:
775
 
            # We need a parent, or we always compare with NULL
776
 
            tree.commit('init')
 
596
            tree.commit('init') # We need a parent, or we always compare with NULL
777
597
            state = tree.current_dirstate()
778
598
            state._read_dirblocks_if_needed()
779
599
            # Now add in an invalid entry, a rename with a dangling pointer
852
672
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
853
673
            ('dir', [(('dir', 'file', 'file-id'), ['a', 'f'])]),
854
674
        ],  self.get_simple_dirblocks(state))
855
 
 
856
 
 
857
 
class TestInventoryCoherency(TestCaseWithTransport):
858
 
 
859
 
    def test_inventory_is_synced_when_unversioning_a_dir(self):
860
 
        """Unversioning the root of a subtree unversions the entire subtree."""
861
 
        tree = self.make_branch_and_tree('.')
862
 
        self.build_tree(['a/', 'a/b', 'c/'])
863
 
        tree.add(['a', 'a/b', 'c'], ['a-id', 'b-id', 'c-id'])
864
 
        # within a lock unversion should take effect
865
 
        tree.lock_write()
866
 
        self.addCleanup(tree.unlock)
867
 
        # Force access to the in memory inventory to trigger bug #494221: try
868
 
        # maintaining the in-memory inventory
869
 
        inv = tree.root_inventory
870
 
        self.assertTrue(inv.has_id('a-id'))
871
 
        self.assertTrue(inv.has_id('b-id'))
872
 
        tree.unversion(['a-id', 'b-id'])
873
 
        self.assertFalse(inv.has_id('a-id'))
874
 
        self.assertFalse(inv.has_id('b-id'))