~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_workingtree.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-09-25 01:24:45 UTC
  • mfrom: (2037.1.2 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060925012445-1cc0dd1824c4b731
(Robert Collins) Add 0.12 deprecation symbol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# (C) 2005 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
15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
 
 
18
from cStringIO import StringIO
18
19
import os
 
20
 
 
21
from bzrlib import ignores
 
22
import bzrlib
19
23
from bzrlib.branch import Branch
 
24
from bzrlib import bzrdir, conflicts, errors, workingtree
 
25
from bzrlib.bzrdir import BzrDir
20
26
from bzrlib.errors import NotBranchError, NotVersionedError
21
 
from bzrlib.tests import TestCaseInTempDir
 
27
from bzrlib.lockdir import LockDir
 
28
from bzrlib.mutabletree import needs_tree_write_lock
 
29
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
 
30
from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
22
31
from bzrlib.trace import mutter
23
 
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
24
 
                                WorkingTree)
 
32
from bzrlib.transport import get_transport
 
33
from bzrlib.workingtree import (
 
34
    TreeEntry,
 
35
    TreeDirectory,
 
36
    TreeFile,
 
37
    TreeLink,
 
38
    WorkingTree,
 
39
    )
25
40
 
26
 
class TestTreeDirectory(TestCaseInTempDir):
 
41
class TestTreeDirectory(TestCaseWithTransport):
27
42
 
28
43
    def test_kind_character(self):
29
44
        self.assertEqual(TreeDirectory().kind_character(), '/')
30
45
 
31
46
 
32
 
class TestTreeEntry(TestCaseInTempDir):
 
47
class TestTreeEntry(TestCaseWithTransport):
33
48
 
34
49
    def test_kind_character(self):
35
50
        self.assertEqual(TreeEntry().kind_character(), '???')
36
51
 
37
52
 
38
 
class TestTreeFile(TestCaseInTempDir):
 
53
class TestTreeFile(TestCaseWithTransport):
39
54
 
40
55
    def test_kind_character(self):
41
56
        self.assertEqual(TreeFile().kind_character(), '')
42
57
 
43
58
 
44
 
class TestTreeLink(TestCaseInTempDir):
 
59
class TestTreeLink(TestCaseWithTransport):
45
60
 
46
61
    def test_kind_character(self):
47
62
        self.assertEqual(TreeLink().kind_character(), '')
48
63
 
49
64
 
50
 
class TestWorkingTree(TestCaseInTempDir):
51
 
 
52
 
    def test_listfiles(self):
53
 
        branch = Branch.initialize(u'.')
54
 
        os.mkdir('dir')
55
 
        print >> open('file', 'w'), "content"
56
 
        os.symlink('target', 'symlink')
57
 
        tree = branch.working_tree()
58
 
        files = list(tree.list_files())
59
 
        self.assertEqual(files[0], ('dir', '?', 'directory', None, TreeDirectory()))
60
 
        self.assertEqual(files[1], ('file', '?', 'file', None, TreeFile()))
61
 
        self.assertEqual(files[2], ('symlink', '?', 'symlink', None, TreeLink()))
62
 
 
63
 
    def test_open_containing(self):
64
 
        branch = Branch.initialize(u'.')
65
 
        wt, relpath = WorkingTree.open_containing()
66
 
        self.assertEqual('', relpath)
67
 
        self.assertEqual(wt.basedir, branch.base)
68
 
        wt, relpath = WorkingTree.open_containing(u'.')
69
 
        self.assertEqual('', relpath)
70
 
        self.assertEqual(wt.basedir, branch.base)
71
 
        wt, relpath = WorkingTree.open_containing('./foo')
72
 
        self.assertEqual('foo', relpath)
73
 
        self.assertEqual(wt.basedir, branch.base)
74
 
        # paths that are urls are just plain wrong for working trees.
75
 
        self.assertRaises(NotBranchError,
76
 
                          WorkingTree.open_containing, 
77
 
                          'file:///' + os.getcwdu())
78
 
 
79
 
    def test_construct_with_branch(self):
80
 
        branch = Branch.initialize(u'.')
81
 
        tree = WorkingTree(branch.base, branch)
82
 
        self.assertEqual(branch, tree.branch)
83
 
        self.assertEqual(branch.base, tree.basedir)
 
65
class TestDefaultFormat(TestCaseWithTransport):
 
66
 
 
67
    def test_get_set_default_format(self):
 
68
        old_format = workingtree.WorkingTreeFormat.get_default_format()
 
69
        # default is 3
 
70
        self.assertTrue(isinstance(old_format, workingtree.WorkingTreeFormat3))
 
71
        workingtree.WorkingTreeFormat.set_default_format(SampleTreeFormat())
 
72
        try:
 
73
            # the default branch format is used by the meta dir format
 
74
            # which is not the default bzrdir format at this point
 
75
            dir = bzrdir.BzrDirMetaFormat1().initialize('.')
 
76
            dir.create_repository()
 
77
            dir.create_branch()
 
78
            result = dir.create_workingtree()
 
79
            self.assertEqual(result, 'A tree')
 
80
        finally:
 
81
            workingtree.WorkingTreeFormat.set_default_format(old_format)
 
82
        self.assertEqual(old_format, workingtree.WorkingTreeFormat.get_default_format())
 
83
 
 
84
 
 
85
class SampleTreeFormat(workingtree.WorkingTreeFormat):
 
86
    """A sample format
 
87
 
 
88
    this format is initializable, unsupported to aid in testing the 
 
89
    open and open_downlevel routines.
 
90
    """
 
91
 
 
92
    def get_format_string(self):
 
93
        """See WorkingTreeFormat.get_format_string()."""
 
94
        return "Sample tree format."
 
95
 
 
96
    def initialize(self, a_bzrdir, revision_id=None):
 
97
        """Sample branches cannot be created."""
 
98
        t = a_bzrdir.get_workingtree_transport(self)
 
99
        t.put_bytes('format', self.get_format_string())
 
100
        return 'A tree'
 
101
 
 
102
    def is_supported(self):
 
103
        return False
 
104
 
 
105
    def open(self, transport, _found=False):
 
106
        return "opened tree."
 
107
 
 
108
 
 
109
class TestWorkingTreeFormat(TestCaseWithTransport):
 
110
    """Tests for the WorkingTreeFormat facility."""
 
111
 
 
112
    def test_find_format(self):
 
113
        # is the right format object found for a working tree?
 
114
        # create a branch with a few known format objects.
 
115
        self.build_tree(["foo/", "bar/"])
 
116
        def check_format(format, url):
 
117
            dir = format._matchingbzrdir.initialize(url)
 
118
            dir.create_repository()
 
119
            dir.create_branch()
 
120
            format.initialize(dir)
 
121
            t = get_transport(url)
 
122
            found_format = workingtree.WorkingTreeFormat.find_format(dir)
 
123
            self.failUnless(isinstance(found_format, format.__class__))
 
124
        check_format(workingtree.WorkingTreeFormat3(), "bar")
 
125
        
 
126
    def test_find_format_no_tree(self):
 
127
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
 
128
        self.assertRaises(errors.NoWorkingTree,
 
129
                          workingtree.WorkingTreeFormat.find_format,
 
130
                          dir)
 
131
 
 
132
    def test_find_format_unknown_format(self):
 
133
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
 
134
        dir.create_repository()
 
135
        dir.create_branch()
 
136
        SampleTreeFormat().initialize(dir)
 
137
        self.assertRaises(errors.UnknownFormatError,
 
138
                          workingtree.WorkingTreeFormat.find_format,
 
139
                          dir)
 
140
 
 
141
    def test_register_unregister_format(self):
 
142
        format = SampleTreeFormat()
 
143
        # make a control dir
 
144
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
 
145
        dir.create_repository()
 
146
        dir.create_branch()
 
147
        # make a branch
 
148
        format.initialize(dir)
 
149
        # register a format for it.
 
150
        workingtree.WorkingTreeFormat.register_format(format)
 
151
        # which branch.Open will refuse (not supported)
 
152
        self.assertRaises(errors.UnsupportedFormatError, workingtree.WorkingTree.open, '.')
 
153
        # but open_downlevel will work
 
154
        self.assertEqual(format.open(dir), workingtree.WorkingTree.open_downlevel('.'))
 
155
        # unregister the format
 
156
        workingtree.WorkingTreeFormat.unregister_format(format)
 
157
 
 
158
 
 
159
class TestWorkingTreeFormat3(TestCaseWithTransport):
 
160
    """Tests specific to WorkingTreeFormat3."""
 
161
 
 
162
    def test_disk_layout(self):
 
163
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
164
        control.create_repository()
 
165
        control.create_branch()
 
166
        tree = workingtree.WorkingTreeFormat3().initialize(control)
 
167
        # we want:
 
168
        # format 'Bazaar-NG Working Tree format 3'
 
169
        # inventory = blank inventory
 
170
        # pending-merges = ''
 
171
        # stat-cache = ??
 
172
        # no inventory.basis yet
 
173
        t = control.get_workingtree_transport(None)
 
174
        self.assertEqualDiff('Bazaar-NG Working Tree format 3',
 
175
                             t.get('format').read())
 
176
        self.assertEqualDiff('<inventory format="5">\n'
 
177
                             '</inventory>\n',
 
178
                             t.get('inventory').read())
 
179
        self.assertEqualDiff('### bzr hashcache v5\n',
 
180
                             t.get('stat-cache').read())
 
181
        self.assertFalse(t.has('inventory.basis'))
 
182
        # no last-revision file means 'None' or 'NULLREVISION'
 
183
        self.assertFalse(t.has('last-revision'))
 
184
        # TODO RBC 20060210 do a commit, check the inventory.basis is created 
 
185
        # correctly and last-revision file becomes present.
 
186
 
 
187
    def test_uses_lockdir(self):
 
188
        """WorkingTreeFormat3 uses its own LockDir:
 
189
            
 
190
            - lock is a directory
 
191
            - when the WorkingTree is locked, LockDir can see that
 
192
        """
 
193
        t = self.get_transport()
 
194
        url = self.get_url()
 
195
        dir = bzrdir.BzrDirMetaFormat1().initialize(url)
 
196
        repo = dir.create_repository()
 
197
        branch = dir.create_branch()
 
198
        try:
 
199
            tree = workingtree.WorkingTreeFormat3().initialize(dir)
 
200
        except errors.NotLocalUrl:
 
201
            raise TestSkipped('Not a local URL')
 
202
        self.assertIsDirectory('.bzr', t)
 
203
        self.assertIsDirectory('.bzr/checkout', t)
 
204
        self.assertIsDirectory('.bzr/checkout/lock', t)
 
205
        our_lock = LockDir(t, '.bzr/checkout/lock')
 
206
        self.assertEquals(our_lock.peek(), None)
 
207
        tree.lock_write()
 
208
        self.assertTrue(our_lock.peek())
 
209
        tree.unlock()
 
210
        self.assertEquals(our_lock.peek(), None)
 
211
 
 
212
    def test_missing_pending_merges(self):
 
213
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
214
        control.create_repository()
 
215
        control.create_branch()
 
216
        tree = workingtree.WorkingTreeFormat3().initialize(control)
 
217
        tree._control_files._transport.delete("pending-merges")
 
218
        self.assertEqual([], tree.get_parent_ids())
 
219
 
 
220
 
 
221
class TestFormat2WorkingTree(TestCaseWithTransport):
 
222
    """Tests that are specific to format 2 trees."""
 
223
 
 
224
    def create_format2_tree(self, url):
 
225
        return self.make_branch_and_tree(
 
226
            url, format=bzrlib.bzrdir.BzrDirFormat6())
 
227
 
 
228
    def test_conflicts(self):
 
229
        # test backwards compatability
 
230
        tree = self.create_format2_tree('.')
 
231
        self.assertRaises(errors.UnsupportedOperation, tree.set_conflicts,
 
232
                          None)
 
233
        file('lala.BASE', 'wb').write('labase')
 
234
        expected = conflicts.ContentsConflict('lala')
 
235
        self.assertEqual(list(tree.conflicts()), [expected])
 
236
        file('lala', 'wb').write('la')
 
237
        tree.add('lala', 'lala-id')
 
238
        expected = conflicts.ContentsConflict('lala', file_id='lala-id')
 
239
        self.assertEqual(list(tree.conflicts()), [expected])
 
240
        file('lala.THIS', 'wb').write('lathis')
 
241
        file('lala.OTHER', 'wb').write('laother')
 
242
        # When "text conflict"s happen, stem, THIS and OTHER are text
 
243
        expected = conflicts.TextConflict('lala', file_id='lala-id')
 
244
        self.assertEqual(list(tree.conflicts()), [expected])
 
245
        os.unlink('lala.OTHER')
 
246
        os.mkdir('lala.OTHER')
 
247
        expected = conflicts.ContentsConflict('lala', file_id='lala-id')
 
248
        self.assertEqual(list(tree.conflicts()), [expected])
 
249
 
 
250
 
 
251
class TestNonFormatSpecificCode(TestCaseWithTransport):
 
252
    """This class contains tests of workingtree that are not format specific."""
 
253
 
84
254
    
85
 
    def test_construct_without_branch(self):
86
 
        branch = Branch.initialize(u'.')
87
 
        tree = WorkingTree(branch.base)
88
 
        self.assertEqual(branch.base, tree.branch.base)
89
 
        self.assertEqual(branch.base, tree.basedir)
90
 
 
91
 
    def test_basic_relpath(self):
92
 
        # for comprehensive relpath tests, see whitebox.py.
93
 
        branch = Branch.initialize(u'.')
94
 
        tree = WorkingTree(branch.base)
95
 
        self.assertEqual('child',
96
 
                         tree.relpath(os.path.join(os.getcwd(), 'child')))
97
 
 
98
 
    def test_lock_locks_branch(self):
99
 
        branch = Branch.initialize(u'.')
100
 
        tree = WorkingTree(branch.base)
101
 
        tree.lock_read()
102
 
        self.assertEqual(1, tree.branch._lock_count)
103
 
        self.assertEqual('r', tree.branch._lock_mode)
104
 
        tree.unlock()
105
 
        self.assertEqual(None, tree.branch._lock_count)
106
 
        tree.lock_write()
107
 
        self.assertEqual(1, tree.branch._lock_count)
108
 
        self.assertEqual('w', tree.branch._lock_mode)
109
 
        tree.unlock()
110
 
        self.assertEqual(None, tree.branch._lock_count)
111
 
 
112
 
    def get_pullable_branches(self):
113
 
        self.build_tree(['from/', 'from/file', 'to/'])
114
 
        br_a = Branch.initialize('from')
115
 
        tree = br_a.working_tree()
116
 
        tree.add('file')
117
 
        tree.commit('foo', rev_id='A')
118
 
        br_b = Branch.initialize('to')
119
 
        return br_a, br_b
120
 
 
121
 
    def test_pull(self):
122
 
        br_a, br_b = self.get_pullable_branches()
123
 
        br_b.working_tree().pull(br_a)
124
 
        self.failUnless(br_b.has_revision('A'))
125
 
        self.assertEqual(['A'], br_b.revision_history())
126
 
 
127
 
    def test_pull_overwrites(self):
128
 
        br_a, br_b = self.get_pullable_branches()
129
 
        br_b.working_tree().commit('foo', rev_id='B')
130
 
        self.assertEqual(['B'], br_b.revision_history())
131
 
        br_b.working_tree().pull(br_a, overwrite=True)
132
 
        self.failUnless(br_b.has_revision('A'))
133
 
        self.failUnless(br_b.has_revision('B'))
134
 
        self.assertEqual(['A'], br_b.revision_history())
135
 
 
136
 
    def test_revert(self):
137
 
        """Test selected-file revert"""
138
 
        b = Branch.initialize(u'.')
139
 
 
140
 
        self.build_tree(['hello.txt'])
141
 
        file('hello.txt', 'w').write('initial hello')
142
 
 
143
 
        self.assertRaises(NotVersionedError,
144
 
                          b.working_tree().revert, ['hello.txt'])
145
 
        tree = WorkingTree(b.base, b)
146
 
        tree.add(['hello.txt'])
147
 
        tree.commit('create initial hello.txt')
148
 
 
149
 
        self.check_file_contents('hello.txt', 'initial hello')
150
 
        file('hello.txt', 'w').write('new hello')
151
 
        self.check_file_contents('hello.txt', 'new hello')
152
 
 
153
 
        # revert file modified since last revision
154
 
        tree.revert(['hello.txt'])
155
 
        self.check_file_contents('hello.txt', 'initial hello')
156
 
        self.check_file_contents('hello.txt~', 'new hello')
157
 
 
158
 
        # reverting again does not clobber the backup
159
 
        tree.revert(['hello.txt'])
160
 
        self.check_file_contents('hello.txt', 'initial hello')
161
 
        self.check_file_contents('hello.txt~', 'new hello')
162
 
 
163
 
    def test_unknowns(self):
164
 
        b = Branch.initialize(u'.')
165
 
        tree = WorkingTree(u'.', b)
166
 
        self.build_tree(['hello.txt',
167
 
                         'hello.txt~'])
168
 
        self.assertEquals(list(tree.unknowns()),
169
 
                          ['hello.txt'])
170
 
 
 
255
    def test_gen_file_id(self):
 
256
        gen_file_id = bzrlib.workingtree.gen_file_id
 
257
 
 
258
        # We try to use the filename if possible
 
259
        self.assertStartsWith(gen_file_id('bar'), 'bar-')
 
260
 
 
261
        # but we squash capitalization, and remove non word characters
 
262
        self.assertStartsWith(gen_file_id('Mwoo oof\t m'), 'mwoooofm-')
 
263
 
 
264
        # We also remove leading '.' characters to prevent hidden file-ids
 
265
        self.assertStartsWith(gen_file_id('..gam.py'), 'gam.py-')
 
266
        self.assertStartsWith(gen_file_id('..Mwoo oof\t m'), 'mwoooofm-')
 
267
 
 
268
        # we remove unicode characters, and still don't end up with a 
 
269
        # hidden file id
 
270
        self.assertStartsWith(gen_file_id(u'\xe5\xb5.txt'), 'txt-')
 
271
        
 
272
        # Our current method of generating unique ids adds 33 characters
 
273
        # plus an serial number (log10(N) characters)
 
274
        # to the end of the filename. We now restrict the filename portion to
 
275
        # be <= 20 characters, so the maximum length should now be approx < 60
 
276
 
 
277
        # Test both case squashing and length restriction
 
278
        fid = gen_file_id('A'*50 + '.txt')
 
279
        self.assertStartsWith(fid, 'a'*20 + '-')
 
280
        self.failUnless(len(fid) < 60)
 
281
 
 
282
        # restricting length happens after the other actions, so
 
283
        # we preserve as much as possible
 
284
        fid = gen_file_id('\xe5\xb5..aBcd\tefGhijKLMnop\tqrstuvwxyz')
 
285
        self.assertStartsWith(fid, 'abcdefghijklmnopqrst-')
 
286
        self.failUnless(len(fid) < 60)
 
287
 
 
288
    def test_next_id_suffix(self):
 
289
        bzrlib.workingtree._gen_id_suffix = None
 
290
        bzrlib.workingtree._next_id_suffix()
 
291
        self.assertNotEqual(None, bzrlib.workingtree._gen_id_suffix)
 
292
        bzrlib.workingtree._gen_id_suffix = "foo-"
 
293
        bzrlib.workingtree._gen_id_serial = 1
 
294
        self.assertEqual("foo-2", bzrlib.workingtree._next_id_suffix())
 
295
        self.assertEqual("foo-3", bzrlib.workingtree._next_id_suffix())
 
296
        self.assertEqual("foo-4", bzrlib.workingtree._next_id_suffix())
 
297
        self.assertEqual("foo-5", bzrlib.workingtree._next_id_suffix())
 
298
        self.assertEqual("foo-6", bzrlib.workingtree._next_id_suffix())
 
299
        self.assertEqual("foo-7", bzrlib.workingtree._next_id_suffix())
 
300
        self.assertEqual("foo-8", bzrlib.workingtree._next_id_suffix())
 
301
        self.assertEqual("foo-9", bzrlib.workingtree._next_id_suffix())
 
302
        self.assertEqual("foo-10", bzrlib.workingtree._next_id_suffix())
 
303
 
 
304
    def test__translate_ignore_rule(self):
 
305
        tree = self.make_branch_and_tree('.')
 
306
        # translation should return the regex, the number of groups in it,
 
307
        # and the original rule in a tuple.
 
308
        # there are three sorts of ignore rules:
 
309
        # root only - regex is the rule itself without the leading ./
 
310
        self.assertEqual(
 
311
            "(rootdirrule$)", 
 
312
            tree._translate_ignore_rule("./rootdirrule"))
 
313
        # full path - regex is the rule itself
 
314
        self.assertEqual(
 
315
            "(path\\/to\\/file$)",
 
316
            tree._translate_ignore_rule("path/to/file"))
 
317
        # basename only rule - regex is a rule that ignores everything up
 
318
        # to the last / in the filename
 
319
        self.assertEqual(
 
320
            "((?:.*/)?(?!.*/)basenamerule$)",
 
321
            tree._translate_ignore_rule("basenamerule"))
 
322
 
 
323
    def test__combine_ignore_rules(self):
 
324
        tree = self.make_branch_and_tree('.')
 
325
        # the combined ignore regexs need the outer group indices
 
326
        # placed in a dictionary with the rules that were combined.
 
327
        # an empty set of rules
 
328
        # this is returned as a list of combined regex,rule sets, because
 
329
        # python has a limit of 100 combined regexes.
 
330
        compiled_rules = tree._combine_ignore_rules([])
 
331
        self.assertEqual([], compiled_rules)
 
332
        # one of each type of rule.
 
333
        compiled_rules = tree._combine_ignore_rules(
 
334
            ["rule1", "rule/two", "./three"])[0]
 
335
        # what type *is* the compiled regex to do an isinstance of ?
 
336
        self.assertEqual(3, compiled_rules[0].groups)
 
337
        self.assertEqual(
 
338
            {0:"rule1",1:"rule/two",2:"./three"},
 
339
            compiled_rules[1])
 
340
 
 
341
    def test__combine_ignore_rules_grouping(self):
 
342
        tree = self.make_branch_and_tree('.')
 
343
        # when there are too many rules, the output is split into groups of 100
 
344
        rules = []
 
345
        for index in range(198):
 
346
            rules.append('foo')
 
347
        self.assertEqual(2, len(tree._combine_ignore_rules(rules)))
 
348
 
 
349
    def test__get_ignore_rules_as_regex(self):
 
350
        tree = self.make_branch_and_tree('.')
 
351
        # Setup the default ignore list to be empty
 
352
        ignores._set_user_ignores([])
 
353
 
 
354
        # some plugins (shelf) modifies the DEFAULT_IGNORE list in memory
 
355
        # which causes this test to fail so force the DEFAULT_IGNORE
 
356
        # list to be empty
 
357
        orig_default = bzrlib.DEFAULT_IGNORE
 
358
        # Also make sure the runtime ignore list is empty
 
359
        orig_runtime = ignores._runtime_ignores
 
360
        try:
 
361
            bzrlib.DEFAULT_IGNORE = []
 
362
            ignores._runtime_ignores = set()
 
363
 
 
364
            self.build_tree_contents([('.bzrignore', 'CVS\n.hg\n')])
 
365
            reference_output = tree._combine_ignore_rules(
 
366
                                    set(['CVS', '.hg']))[0]
 
367
            regex_rules = tree._get_ignore_rules_as_regex()[0]
 
368
            self.assertEqual(len(reference_output[1]), regex_rules[0].groups)
 
369
            self.assertEqual(reference_output[1], regex_rules[1])
 
370
        finally:
 
371
            bzrlib.DEFAULT_IGNORE = orig_default
 
372
            ignores._runtime_ignores = orig_runtime
 
373
 
 
374
 
 
375
class InstrumentedTree(object):
 
376
    """A instrumented tree to check the needs_tree_write_lock decorator."""
 
377
 
 
378
    def __init__(self):
 
379
        self._locks = []
 
380
 
 
381
    def lock_tree_write(self):
 
382
        self._locks.append('t')
 
383
 
 
384
    @needs_tree_write_lock
 
385
    def method_with_tree_write_lock(self, *args, **kwargs):
 
386
        """A lock_tree_write decorated method that returns its arguments."""
 
387
        return args, kwargs
 
388
 
 
389
    @needs_tree_write_lock
 
390
    def method_that_raises(self):
 
391
        """This method causes an exception when called with parameters.
 
392
        
 
393
        This allows the decorator code to be checked - it should still call
 
394
        unlock.
 
395
        """
 
396
 
 
397
    def unlock(self):
 
398
        self._locks.append('u')
 
399
 
 
400
 
 
401
class TestInstrumentedTree(TestCase):
 
402
 
 
403
    def test_needs_tree_write_lock(self):
 
404
        """@needs_tree_write_lock should be semantically transparent."""
 
405
        tree = InstrumentedTree()
 
406
        self.assertEqual(
 
407
            'method_with_tree_write_lock',
 
408
            tree.method_with_tree_write_lock.__name__)
 
409
        self.assertEqual(
 
410
            "A lock_tree_write decorated method that returns its arguments.",
 
411
            tree.method_with_tree_write_lock.__doc__)
 
412
        args = (1, 2, 3)
 
413
        kwargs = {'a':'b'}
 
414
        result = tree.method_with_tree_write_lock(1,2,3, a='b')
 
415
        self.assertEqual((args, kwargs), result)
 
416
        self.assertEqual(['t', 'u'], tree._locks)
 
417
        self.assertRaises(TypeError, tree.method_that_raises, 'foo')
 
418
        self.assertEqual(['t', 'u', 't', 'u'], tree._locks)