~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/testworkingtree.py

- avoid warning about log not being registered during startup

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# (C) 2005 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
19
18
import os
20
 
 
21
 
from bzrlib import ignores
22
 
import bzrlib
23
19
from bzrlib.branch import Branch
24
 
from bzrlib import bzrdir, conflicts, errors, workingtree
25
 
from bzrlib.bzrdir import BzrDir
26
 
from bzrlib.errors import NotBranchError, NotVersionedError
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
 
20
from bzrlib.selftest import TestCaseInTempDir
31
21
from bzrlib.trace import mutter
32
 
from bzrlib.transport import get_transport
33
 
from bzrlib.workingtree import (
34
 
    TreeEntry,
35
 
    TreeDirectory,
36
 
    TreeFile,
37
 
    TreeLink,
38
 
    WorkingTree,
39
 
    )
 
22
from bzrlib.workingtree import TreeEntry, TreeDirectory, TreeFile, TreeLink
40
23
 
41
 
class TestTreeDirectory(TestCaseWithTransport):
 
24
class TestTreeDirectory(TestCaseInTempDir):
42
25
 
43
26
    def test_kind_character(self):
44
27
        self.assertEqual(TreeDirectory().kind_character(), '/')
45
28
 
46
29
 
47
 
class TestTreeEntry(TestCaseWithTransport):
 
30
class TestTreeEntry(TestCaseInTempDir):
48
31
 
49
32
    def test_kind_character(self):
50
33
        self.assertEqual(TreeEntry().kind_character(), '???')
51
34
 
52
35
 
53
 
class TestTreeFile(TestCaseWithTransport):
 
36
class TestTreeFile(TestCaseInTempDir):
54
37
 
55
38
    def test_kind_character(self):
56
39
        self.assertEqual(TreeFile().kind_character(), '')
57
40
 
58
41
 
59
 
class TestTreeLink(TestCaseWithTransport):
 
42
class TestTreeLink(TestCaseInTempDir):
60
43
 
61
44
    def test_kind_character(self):
62
45
        self.assertEqual(TreeLink().kind_character(), '')
63
46
 
64
47
 
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.assertContainsRe(t.get('inventory').read(), 
177
 
        #                       '<inventory file_id="[^"]*" format="5">\n'
178
 
        #                       '</inventory>\n',
179
 
        #                      )
180
 
        # WorkingTreeFormat3 doesn't default to creating a unique root id,
181
 
        # because it is incompatible with older bzr versions
182
 
        self.assertContainsRe(t.get('inventory').read(),
183
 
                              '<inventory format="5">\n'
184
 
                              '</inventory>\n',
185
 
                             )
186
 
        self.assertEqualDiff('### bzr hashcache v5\n',
187
 
                             t.get('stat-cache').read())
188
 
        self.assertFalse(t.has('inventory.basis'))
189
 
        # no last-revision file means 'None' or 'NULLREVISION'
190
 
        self.assertFalse(t.has('last-revision'))
191
 
        # TODO RBC 20060210 do a commit, check the inventory.basis is created 
192
 
        # correctly and last-revision file becomes present.
193
 
 
194
 
    def test_uses_lockdir(self):
195
 
        """WorkingTreeFormat3 uses its own LockDir:
196
 
            
197
 
            - lock is a directory
198
 
            - when the WorkingTree is locked, LockDir can see that
199
 
        """
200
 
        t = self.get_transport()
201
 
        url = self.get_url()
202
 
        dir = bzrdir.BzrDirMetaFormat1().initialize(url)
203
 
        repo = dir.create_repository()
204
 
        branch = dir.create_branch()
205
 
        try:
206
 
            tree = workingtree.WorkingTreeFormat3().initialize(dir)
207
 
        except errors.NotLocalUrl:
208
 
            raise TestSkipped('Not a local URL')
209
 
        self.assertIsDirectory('.bzr', t)
210
 
        self.assertIsDirectory('.bzr/checkout', t)
211
 
        self.assertIsDirectory('.bzr/checkout/lock', t)
212
 
        our_lock = LockDir(t, '.bzr/checkout/lock')
213
 
        self.assertEquals(our_lock.peek(), None)
214
 
        tree.lock_write()
215
 
        self.assertTrue(our_lock.peek())
216
 
        tree.unlock()
217
 
        self.assertEquals(our_lock.peek(), None)
218
 
 
219
 
    def test_missing_pending_merges(self):
220
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
221
 
        control.create_repository()
222
 
        control.create_branch()
223
 
        tree = workingtree.WorkingTreeFormat3().initialize(control)
224
 
        tree._control_files._transport.delete("pending-merges")
225
 
        self.assertEqual([], tree.get_parent_ids())
226
 
 
227
 
 
228
 
class TestFormat2WorkingTree(TestCaseWithTransport):
229
 
    """Tests that are specific to format 2 trees."""
230
 
 
231
 
    def create_format2_tree(self, url):
232
 
        return self.make_branch_and_tree(
233
 
            url, format=bzrlib.bzrdir.BzrDirFormat6())
234
 
 
235
 
    def test_conflicts(self):
236
 
        # test backwards compatability
237
 
        tree = self.create_format2_tree('.')
238
 
        self.assertRaises(errors.UnsupportedOperation, tree.set_conflicts,
239
 
                          None)
240
 
        file('lala.BASE', 'wb').write('labase')
241
 
        expected = conflicts.ContentsConflict('lala')
242
 
        self.assertEqual(list(tree.conflicts()), [expected])
243
 
        file('lala', 'wb').write('la')
244
 
        tree.add('lala', 'lala-id')
245
 
        expected = conflicts.ContentsConflict('lala', file_id='lala-id')
246
 
        self.assertEqual(list(tree.conflicts()), [expected])
247
 
        file('lala.THIS', 'wb').write('lathis')
248
 
        file('lala.OTHER', 'wb').write('laother')
249
 
        # When "text conflict"s happen, stem, THIS and OTHER are text
250
 
        expected = conflicts.TextConflict('lala', file_id='lala-id')
251
 
        self.assertEqual(list(tree.conflicts()), [expected])
252
 
        os.unlink('lala.OTHER')
253
 
        os.mkdir('lala.OTHER')
254
 
        expected = conflicts.ContentsConflict('lala', file_id='lala-id')
255
 
        self.assertEqual(list(tree.conflicts()), [expected])
256
 
 
257
 
 
258
 
class TestNonFormatSpecificCode(TestCaseWithTransport):
259
 
    """This class contains tests of workingtree that are not format specific."""
260
 
 
261
 
    
262
 
    def test_gen_file_id(self):
263
 
        gen_file_id = bzrlib.workingtree.gen_file_id
264
 
 
265
 
        # We try to use the filename if possible
266
 
        self.assertStartsWith(gen_file_id('bar'), 'bar-')
267
 
 
268
 
        # but we squash capitalization, and remove non word characters
269
 
        self.assertStartsWith(gen_file_id('Mwoo oof\t m'), 'mwoooofm-')
270
 
 
271
 
        # We also remove leading '.' characters to prevent hidden file-ids
272
 
        self.assertStartsWith(gen_file_id('..gam.py'), 'gam.py-')
273
 
        self.assertStartsWith(gen_file_id('..Mwoo oof\t m'), 'mwoooofm-')
274
 
 
275
 
        # we remove unicode characters, and still don't end up with a 
276
 
        # hidden file id
277
 
        self.assertStartsWith(gen_file_id(u'\xe5\xb5.txt'), 'txt-')
278
 
        
279
 
        # Our current method of generating unique ids adds 33 characters
280
 
        # plus an serial number (log10(N) characters)
281
 
        # to the end of the filename. We now restrict the filename portion to
282
 
        # be <= 20 characters, so the maximum length should now be approx < 60
283
 
 
284
 
        # Test both case squashing and length restriction
285
 
        fid = gen_file_id('A'*50 + '.txt')
286
 
        self.assertStartsWith(fid, 'a'*20 + '-')
287
 
        self.failUnless(len(fid) < 60)
288
 
 
289
 
        # restricting length happens after the other actions, so
290
 
        # we preserve as much as possible
291
 
        fid = gen_file_id('\xe5\xb5..aBcd\tefGhijKLMnop\tqrstuvwxyz')
292
 
        self.assertStartsWith(fid, 'abcdefghijklmnopqrst-')
293
 
        self.failUnless(len(fid) < 60)
294
 
 
295
 
    def test_next_id_suffix(self):
296
 
        bzrlib.workingtree._gen_id_suffix = None
297
 
        bzrlib.workingtree._next_id_suffix()
298
 
        self.assertNotEqual(None, bzrlib.workingtree._gen_id_suffix)
299
 
        bzrlib.workingtree._gen_id_suffix = "foo-"
300
 
        bzrlib.workingtree._gen_id_serial = 1
301
 
        self.assertEqual("foo-2", bzrlib.workingtree._next_id_suffix())
302
 
        self.assertEqual("foo-3", bzrlib.workingtree._next_id_suffix())
303
 
        self.assertEqual("foo-4", bzrlib.workingtree._next_id_suffix())
304
 
        self.assertEqual("foo-5", bzrlib.workingtree._next_id_suffix())
305
 
        self.assertEqual("foo-6", bzrlib.workingtree._next_id_suffix())
306
 
        self.assertEqual("foo-7", bzrlib.workingtree._next_id_suffix())
307
 
        self.assertEqual("foo-8", bzrlib.workingtree._next_id_suffix())
308
 
        self.assertEqual("foo-9", bzrlib.workingtree._next_id_suffix())
309
 
        self.assertEqual("foo-10", bzrlib.workingtree._next_id_suffix())
310
 
 
311
 
    def test__translate_ignore_rule(self):
312
 
        tree = self.make_branch_and_tree('.')
313
 
        # translation should return the regex, the number of groups in it,
314
 
        # and the original rule in a tuple.
315
 
        # there are three sorts of ignore rules:
316
 
        # root only - regex is the rule itself without the leading ./
317
 
        self.assertEqual(
318
 
            "(rootdirrule$)", 
319
 
            tree._translate_ignore_rule("./rootdirrule"))
320
 
        # full path - regex is the rule itself
321
 
        self.assertEqual(
322
 
            "(path\\/to\\/file$)",
323
 
            tree._translate_ignore_rule("path/to/file"))
324
 
        # basename only rule - regex is a rule that ignores everything up
325
 
        # to the last / in the filename
326
 
        self.assertEqual(
327
 
            "((?:.*/)?(?!.*/)basenamerule$)",
328
 
            tree._translate_ignore_rule("basenamerule"))
329
 
 
330
 
    def test__combine_ignore_rules(self):
331
 
        tree = self.make_branch_and_tree('.')
332
 
        # the combined ignore regexs need the outer group indices
333
 
        # placed in a dictionary with the rules that were combined.
334
 
        # an empty set of rules
335
 
        # this is returned as a list of combined regex,rule sets, because
336
 
        # python has a limit of 100 combined regexes.
337
 
        compiled_rules = tree._combine_ignore_rules([])
338
 
        self.assertEqual([], compiled_rules)
339
 
        # one of each type of rule.
340
 
        compiled_rules = tree._combine_ignore_rules(
341
 
            ["rule1", "rule/two", "./three"])[0]
342
 
        # what type *is* the compiled regex to do an isinstance of ?
343
 
        self.assertEqual(3, compiled_rules[0].groups)
344
 
        self.assertEqual(
345
 
            {0:"rule1",1:"rule/two",2:"./three"},
346
 
            compiled_rules[1])
347
 
 
348
 
    def test__combine_ignore_rules_grouping(self):
349
 
        tree = self.make_branch_and_tree('.')
350
 
        # when there are too many rules, the output is split into groups of 100
351
 
        rules = []
352
 
        for index in range(198):
353
 
            rules.append('foo')
354
 
        self.assertEqual(2, len(tree._combine_ignore_rules(rules)))
355
 
 
356
 
    def test__get_ignore_rules_as_regex(self):
357
 
        tree = self.make_branch_and_tree('.')
358
 
        # Setup the default ignore list to be empty
359
 
        ignores._set_user_ignores([])
360
 
 
361
 
        # some plugins (shelf) modifies the DEFAULT_IGNORE list in memory
362
 
        # which causes this test to fail so force the DEFAULT_IGNORE
363
 
        # list to be empty
364
 
        orig_default = bzrlib.DEFAULT_IGNORE
365
 
        # Also make sure the runtime ignore list is empty
366
 
        orig_runtime = ignores._runtime_ignores
367
 
        try:
368
 
            bzrlib.DEFAULT_IGNORE = []
369
 
            ignores._runtime_ignores = set()
370
 
 
371
 
            self.build_tree_contents([('.bzrignore', 'CVS\n.hg\n')])
372
 
            reference_output = tree._combine_ignore_rules(
373
 
                                    set(['CVS', '.hg']))[0]
374
 
            regex_rules = tree._get_ignore_rules_as_regex()[0]
375
 
            self.assertEqual(len(reference_output[1]), regex_rules[0].groups)
376
 
            self.assertEqual(reference_output[1], regex_rules[1])
377
 
        finally:
378
 
            bzrlib.DEFAULT_IGNORE = orig_default
379
 
            ignores._runtime_ignores = orig_runtime
380
 
 
381
 
 
382
 
class InstrumentedTree(object):
383
 
    """A instrumented tree to check the needs_tree_write_lock decorator."""
384
 
 
385
 
    def __init__(self):
386
 
        self._locks = []
387
 
 
388
 
    def lock_tree_write(self):
389
 
        self._locks.append('t')
390
 
 
391
 
    @needs_tree_write_lock
392
 
    def method_with_tree_write_lock(self, *args, **kwargs):
393
 
        """A lock_tree_write decorated method that returns its arguments."""
394
 
        return args, kwargs
395
 
 
396
 
    @needs_tree_write_lock
397
 
    def method_that_raises(self):
398
 
        """This method causes an exception when called with parameters.
399
 
        
400
 
        This allows the decorator code to be checked - it should still call
401
 
        unlock.
402
 
        """
403
 
 
404
 
    def unlock(self):
405
 
        self._locks.append('u')
406
 
 
407
 
 
408
 
class TestInstrumentedTree(TestCase):
409
 
 
410
 
    def test_needs_tree_write_lock(self):
411
 
        """@needs_tree_write_lock should be semantically transparent."""
412
 
        tree = InstrumentedTree()
413
 
        self.assertEqual(
414
 
            'method_with_tree_write_lock',
415
 
            tree.method_with_tree_write_lock.__name__)
416
 
        self.assertEqual(
417
 
            "A lock_tree_write decorated method that returns its arguments.",
418
 
            tree.method_with_tree_write_lock.__doc__)
419
 
        args = (1, 2, 3)
420
 
        kwargs = {'a':'b'}
421
 
        result = tree.method_with_tree_write_lock(1,2,3, a='b')
422
 
        self.assertEqual((args, kwargs), result)
423
 
        self.assertEqual(['t', 'u'], tree._locks)
424
 
        self.assertRaises(TypeError, tree.method_that_raises, 'foo')
425
 
        self.assertEqual(['t', 'u', 't', 'u'], tree._locks)
 
48
class TestWorkingTree(TestCaseInTempDir):
 
49
 
 
50
    def test_listfiles(self):
 
51
        branch = Branch.initialize('.')
 
52
        os.mkdir('dir')
 
53
        print >> open('file', 'w'), "content"
 
54
        os.symlink('target', 'symlink')
 
55
        tree = branch.working_tree()
 
56
        files = list(tree.list_files())
 
57
        self.assertEqual(files[0], ('dir', '?', 'directory', None, TreeDirectory()))
 
58
        self.assertEqual(files[1], ('file', '?', 'file', None, TreeFile()))
 
59
        self.assertEqual(files[2], ('symlink', '?', 'symlink', None, TreeLink()))