~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_globbing.py

Major code cleanup.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2011 Canonical Ltd
 
2
# -*- coding: utf-8 -*-
 
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
17
 
 
18
import re
 
19
 
 
20
from bzrlib import errors
 
21
from bzrlib.globbing import (
 
22
    Globster,
 
23
    ExceptionGlobster,
 
24
    _OrderedGlobster,
 
25
    normalize_pattern
 
26
    )
 
27
from bzrlib.tests import (
 
28
    TestCase,
 
29
    )
 
30
 
 
31
 
 
32
class TestGlobster(TestCase):
 
33
 
 
34
    def assertMatch(self, matchset, glob_prefix=None):
 
35
        for glob, positive, negative in matchset:
 
36
            if glob_prefix:
 
37
                glob = glob_prefix + glob
 
38
            globster = Globster([glob])
 
39
            for name in positive:
 
40
                self.assertTrue(globster.match(name), repr(
 
41
                    u'name "%s" does not match glob "%s" (re=%s)' %
 
42
                    (name, glob, globster._regex_patterns[0][0].pattern)))
 
43
            for name in negative:
 
44
                self.assertFalse(globster.match(name), repr(
 
45
                    u'name "%s" does match glob "%s" (re=%s)' %
 
46
                    (name, glob, globster._regex_patterns[0][0].pattern)))
 
47
 
 
48
    def assertMatchBasenameAndFullpath(self, matchset):
 
49
        # test basename matcher
 
50
        self.assertMatch(matchset)
 
51
        # test fullpath matcher
 
52
        self.assertMatch(matchset, glob_prefix='./')
 
53
 
 
54
    def test_char_group_digit(self):
 
55
        self.assertMatchBasenameAndFullpath([
 
56
            # The definition of digit this uses includes arabic digits from
 
57
            # non-latin scripts (arabic, indic, etc.) but neither roman
 
58
            # numerals nor vulgar fractions. Some characters such as
 
59
            # subscript/superscript digits may or may not match depending on
 
60
            # the Python version used, see: <http://bugs.python.org/issue6561>
 
61
            (u'[[:digit:]]',
 
62
             [u'0', u'5', u'\u0663', u'\u06f9', u'\u0f21'],
 
63
             [u'T', u'q', u' ', u'\u8336', u'.']),
 
64
            (u'[^[:digit:]]',
 
65
             [u'T', u'q', u' ', u'\u8336', u'.'],
 
66
             [u'0', u'5', u'\u0663', u'\u06f9', u'\u0f21']),
 
67
            ])
 
68
 
 
69
    def test_char_group_space(self):
 
70
        self.assertMatchBasenameAndFullpath([
 
71
            (u'[[:space:]]',
 
72
             [u' ', u'\t', u'\n', u'\xa0', u'\u2000', u'\u2002'],
 
73
             [u'a', u'-', u'\u8336', u'.']),
 
74
            (u'[^[:space:]]',
 
75
             [u'a', u'-', u'\u8336', u'.'],
 
76
             [u' ', u'\t', u'\n', u'\xa0', u'\u2000', u'\u2002']),
 
77
            ])
 
78
 
 
79
    def test_char_group_alnum(self):
 
80
        self.assertMatchBasenameAndFullpath([
 
81
            (u'[[:alnum:]]',
 
82
             [u'a', u'Z', u'\u017e', u'\u8336'],
 
83
             [u':', u'-', u'\u25cf', u'.']),
 
84
            (u'[^[:alnum:]]',
 
85
             [u':', u'-', u'\u25cf', u'.'],
 
86
             [u'a']),
 
87
            ])
 
88
 
 
89
    def test_char_group_ascii(self):
 
90
        self.assertMatchBasenameAndFullpath([
 
91
            (u'[[:ascii:]]',
 
92
             [u'a', u'Q', u'^', u'.'],
 
93
             [u'\xcc', u'\u8336']),
 
94
            (u'[^[:ascii:]]',
 
95
             [u'\xcc', u'\u8336'],
 
96
             [u'a', u'Q', u'^', u'.']),
 
97
            ])
 
98
 
 
99
    def test_char_group_blank(self):
 
100
        self.assertMatchBasenameAndFullpath([
 
101
            (u'[[:blank:]]',
 
102
             [u'\t'],
 
103
             [u'x', u'y', u'z', u'.']),
 
104
            (u'[^[:blank:]]',
 
105
             [u'x', u'y', u'z', u'.'],
 
106
             [u'\t']),
 
107
            ])
 
108
 
 
109
    def test_char_group_cntrl(self):
 
110
        self.assertMatchBasenameAndFullpath([
 
111
            (u'[[:cntrl:]]',
 
112
             [u'\b', u'\t', '\x7f'],
 
113
             [u'a', u'Q', u'\u8336', u'.']),
 
114
            (u'[^[:cntrl:]]',
 
115
             [u'a', u'Q', u'\u8336', u'.'],
 
116
             [u'\b', u'\t', '\x7f']),
 
117
            ])
 
118
 
 
119
    def test_char_group_range(self):
 
120
        self.assertMatchBasenameAndFullpath([
 
121
            (u'[a-z]',
 
122
             [u'a', u'q', u'f'],
 
123
             [u'A', u'Q', u'F']),
 
124
            (u'[^a-z]',
 
125
             [u'A', u'Q', u'F'],
 
126
             [u'a', u'q', u'f']),
 
127
            (u'[!a-z]foo',
 
128
             [u'Afoo', u'.foo'],
 
129
             [u'afoo', u'ABfoo']),
 
130
            (u'foo[!a-z]bar',
 
131
             [u'fooAbar', u'foo.bar'],
 
132
             [u'foojbar']),
 
133
            (u'[\x20-\x30\u8336]',
 
134
             [u'\040', u'\044', u'\u8336'],
 
135
             [u'\x1f']),
 
136
            (u'[^\x20-\x30\u8336]',
 
137
             [u'\x1f'],
 
138
             [u'\040', u'\044', u'\u8336']),
 
139
            ])
 
140
 
 
141
    def test_regex(self):
 
142
        self.assertMatch([
 
143
            (u'RE:(a|b|c+)',
 
144
             [u'a', u'b', u'ccc'],
 
145
             [u'd', u'aa', u'c+', u'-a']),
 
146
            (u'RE:(?:a|b|c+)',
 
147
             [u'a', u'b', u'ccc'],
 
148
             [u'd', u'aa', u'c+', u'-a']),
 
149
            (u'RE:(?P<a>.)(?P=a)',
 
150
             [u'a'],
 
151
             [u'ab', u'aa', u'aaa']),
 
152
            # test we can handle odd numbers of trailing backslashes
 
153
            (u'RE:a\\\\\\',
 
154
             [u'a\\'],
 
155
             [u'a', u'ab', u'aa', u'aaa']),
 
156
            ])
 
157
 
 
158
    def test_question_mark(self):
 
159
        self.assertMatch([
 
160
            (u'?foo',
 
161
             [u'xfoo', u'bar/xfoo', u'bar/\u8336foo', u'.foo', u'bar/.foo'],
 
162
             [u'bar/foo', u'foo']),
 
163
            (u'foo?bar',
 
164
             [u'fooxbar', u'foo.bar', u'foo\u8336bar', u'qyzzy/foo.bar'],
 
165
             [u'foo/bar']),
 
166
            (u'foo/?bar',
 
167
             [u'foo/xbar', u'foo/\u8336bar', u'foo/.bar'],
 
168
             [u'foo/bar', u'bar/foo/xbar']),
 
169
            ])
 
170
 
 
171
    def test_asterisk(self):
 
172
        self.assertMatch([
 
173
            (u'x*x',
 
174
             [u'xx', u'x.x', u'x\u8336..x', u'\u8336/x.x', u'x.y.x'],
 
175
             [u'x/x', u'bar/x/bar/x', u'bax/abaxab']),
 
176
            (u'foo/*x',
 
177
             [u'foo/x', u'foo/bax', u'foo/a.x', u'foo/.x', u'foo/.q.x'],
 
178
             [u'foo/bar/bax']),
 
179
            (u'*/*x',
 
180
             [u'\u8336/x', u'foo/x', u'foo/bax', u'x/a.x', u'.foo/x',
 
181
              u'\u8336/.x', u'foo/.q.x'],
 
182
             [u'foo/bar/bax']),
 
183
            (u'f*',
 
184
             [u'foo', u'foo.bar'],
 
185
             [u'.foo', u'foo/bar', u'foo/.bar']),
 
186
            (u'*bar',
 
187
             [u'bar', u'foobar', ur'foo\nbar', u'foo.bar', u'foo/bar',
 
188
              u'foo/foobar', u'foo/f.bar', u'.bar', u'foo/.bar'],
 
189
             []),
 
190
            ])
 
191
 
 
192
    def test_double_asterisk(self):
 
193
        self.assertMatch([
 
194
            # expected uses of double asterisk
 
195
            (u'foo/**/x',
 
196
             [u'foo/x', u'foo/bar/x'],
 
197
             [u'foox', u'foo/bax', u'foo/.x', u'foo/bar/bax']),
 
198
            (u'**/bar',
 
199
             [u'bar', u'foo/bar'],
 
200
             [u'foobar', u'foo.bar', u'foo/foobar', u'foo/f.bar',
 
201
              u'.bar', u'foo/.bar']),
 
202
            # check that we ignore extra *s, so *** is treated like ** not *.
 
203
            (u'foo/***/x',
 
204
             [u'foo/x', u'foo/bar/x'],
 
205
             [u'foox', u'foo/bax', u'foo/.x', u'foo/bar/bax']),
 
206
            (u'***/bar',
 
207
             [u'bar', u'foo/bar'],
 
208
             [u'foobar', u'foo.bar', u'foo/foobar', u'foo/f.bar',
 
209
              u'.bar', u'foo/.bar']),
 
210
            # the remaining tests check that ** is interpreted as *
 
211
            # unless it is a whole path component
 
212
            (u'x**/x',
 
213
             [u'x\u8336/x', u'x/x'],
 
214
             [u'xx', u'x.x', u'bar/x/bar/x', u'x.y.x', u'x/y/x']),
 
215
            (u'x**x',
 
216
             [u'xx', u'x.x', u'x\u8336..x', u'foo/x.x', u'x.y.x'],
 
217
             [u'bar/x/bar/x', u'xfoo/bar/x', u'x/x', u'bax/abaxab']),
 
218
            (u'foo/**x',
 
219
             [u'foo/x', u'foo/bax', u'foo/a.x', u'foo/.x', u'foo/.q.x'],
 
220
             [u'foo/bar/bax']),
 
221
            (u'f**',
 
222
             [u'foo', u'foo.bar'],
 
223
             [u'.foo', u'foo/bar', u'foo/.bar']),
 
224
            (u'**bar',
 
225
             [u'bar', u'foobar', ur'foo\nbar', u'foo.bar', u'foo/bar',
 
226
              u'foo/foobar', u'foo/f.bar', u'.bar', u'foo/.bar'],
 
227
             []),
 
228
            ])
 
229
 
 
230
    def test_leading_dot_slash(self):
 
231
        self.assertMatch([
 
232
            (u'./foo',
 
233
             [u'foo'],
 
234
             [u'\u8336/foo', u'barfoo', u'x/y/foo']),
 
235
            (u'./f*',
 
236
             [u'foo'],
 
237
             [u'foo/bar', u'foo/.bar', u'x/foo/y']),
 
238
            ])
 
239
 
 
240
    def test_backslash(self):
 
241
        self.assertMatch([
 
242
            (u'.\\foo',
 
243
             [u'foo'],
 
244
             [u'\u8336/foo', u'barfoo', u'x/y/foo']),
 
245
            (u'.\\f*',
 
246
             [u'foo'],
 
247
             [u'foo/bar', u'foo/.bar', u'x/foo/y']),
 
248
            (u'foo\\**\\x',
 
249
             [u'foo/x', u'foo/bar/x'],
 
250
             [u'foox', u'foo/bax', u'foo/.x', u'foo/bar/bax']),
 
251
            ])
 
252
 
 
253
    def test_trailing_slash(self):
 
254
        self.assertMatch([
 
255
            (u'./foo/',
 
256
             [u'foo'],
 
257
             [u'\u8336/foo', u'barfoo', u'x/y/foo']),
 
258
            (u'.\\foo\\',
 
259
             [u'foo'],
 
260
             [u'foo/', u'\u8336/foo', u'barfoo', u'x/y/foo']),
 
261
            ])
 
262
 
 
263
    def test_leading_asterisk_dot(self):
 
264
        self.assertMatch([
 
265
            (u'*.x',
 
266
             [u'foo/bar/baz.x', u'\u8336/Q.x', u'foo.y.x', u'.foo.x',
 
267
              u'bar/.foo.x', u'.x',],
 
268
             [u'foo.x.y']),
 
269
            (u'foo/*.bar',
 
270
             [u'foo/b.bar', u'foo/a.b.bar', u'foo/.bar'],
 
271
             [u'foo/bar']),
 
272
            (u'*.~*',
 
273
             [u'foo.py.~1~', u'.foo.py.~1~'],
 
274
             []),
 
275
            ])
 
276
 
 
277
    def test_end_anchor(self):
 
278
        self.assertMatch([
 
279
            (u'*.333',
 
280
             [u'foo.333'],
 
281
             [u'foo.3']),
 
282
            (u'*.3',
 
283
             [u'foo.3'],
 
284
             [u'foo.333']),
 
285
            ])
 
286
 
 
287
    def test_mixed_globs(self):
 
288
        """tests handling of combinations of path type matches.
 
289
 
 
290
        The types being extension, basename and full path.
 
291
        """
 
292
        patterns = [ u'*.foo', u'.*.swp', u'./*.png']
 
293
        globster = Globster(patterns)
 
294
        self.assertEqual(u'*.foo', globster.match('bar.foo'))
 
295
        self.assertEqual(u'./*.png', globster.match('foo.png'))
 
296
        self.assertEqual(None, globster.match('foo/bar.png'))
 
297
        self.assertEqual(u'.*.swp', globster.match('foo/.bar.py.swp'))
 
298
 
 
299
    def test_large_globset(self):
 
300
        """tests that the globster can handle a large set of patterns.
 
301
 
 
302
        Large is defined as more than supported by python regex groups,
 
303
        i.e. 99.
 
304
        This test assumes the globs are broken into regexs containing 99
 
305
        groups.
 
306
        """
 
307
        patterns = [ u'*.%03d' % i for i in xrange(0,300) ]
 
308
        globster = Globster(patterns)
 
309
        # test the fence posts
 
310
        for x in (0,98,99,197,198,296,297,299):
 
311
            filename = u'foo.%03d' % x
 
312
            self.assertEqual(patterns[x],globster.match(filename))
 
313
        self.assertEqual(None,globster.match('foobar.300'))
 
314
 
 
315
    def test_bad_pattern(self):
 
316
        """Ensure that globster handles bad patterns cleanly."""
 
317
        patterns = [u'RE:[', u'/home/foo', u'RE:*.cpp']
 
318
        g = Globster(patterns)
 
319
        e = self.assertRaises(errors.InvalidPattern, g.match, 'filename')
 
320
        self.assertContainsRe(e.msg,
 
321
            "File.*ignore.*contains error.*RE:\[.*RE:\*\.cpp", flags=re.DOTALL)
 
322
 
 
323
 
 
324
class TestExceptionGlobster(TestCase):
 
325
 
 
326
    def test_exclusion_patterns(self):
 
327
        """test that exception patterns are not matched"""
 
328
        patterns = [ u'*', u'!./local', u'!./local/**/*', u'!RE:\.z.*',u'!!./.zcompdump' ]
 
329
        globster = ExceptionGlobster(patterns)
 
330
        self.assertEqual(u'*', globster.match('tmp/foo.txt'))
 
331
        self.assertEqual(None, globster.match('local'))
 
332
        self.assertEqual(None, globster.match('local/bin/wombat'))
 
333
        self.assertEqual(None, globster.match('.zshrc'))
 
334
        self.assertEqual(None, globster.match('.zfunctions/fiddle/flam'))
 
335
        self.assertEqual(u'!!./.zcompdump', globster.match('.zcompdump'))
 
336
 
 
337
    def test_exclusion_order(self):
 
338
        """test that ordering of exclusion patterns does not matter"""
 
339
        patterns = [ u'static/**/*.html', u'!static/**/versionable.html']
 
340
        globster = ExceptionGlobster(patterns)
 
341
        self.assertEqual(u'static/**/*.html', globster.match('static/foo.html'))
 
342
        self.assertEqual(None, globster.match('static/versionable.html'))
 
343
        self.assertEqual(None, globster.match('static/bar/versionable.html'))
 
344
        globster = ExceptionGlobster(reversed(patterns))
 
345
        self.assertEqual(u'static/**/*.html', globster.match('static/foo.html'))
 
346
        self.assertEqual(None, globster.match('static/versionable.html'))
 
347
        self.assertEqual(None, globster.match('static/bar/versionable.html'))
 
348
 
 
349
class TestOrderedGlobster(TestCase):
 
350
 
 
351
    def test_ordered_globs(self):
 
352
        """test that the first match in a list is the one found"""
 
353
        patterns = [ u'*.foo', u'bar.*']
 
354
        globster = _OrderedGlobster(patterns)
 
355
        self.assertEqual(u'*.foo', globster.match('bar.foo'))
 
356
        self.assertEqual(None, globster.match('foo.bar'))
 
357
        globster = _OrderedGlobster(reversed(patterns))
 
358
        self.assertEqual(u'bar.*', globster.match('bar.foo'))
 
359
        self.assertEqual(None, globster.match('foo.bar'))
 
360
 
 
361
 
 
362
class TestNormalizePattern(TestCase):
 
363
 
 
364
    def test_backslashes(self):
 
365
        """tests that backslashes are converted to forward slashes, multiple
 
366
        backslashes are collapsed to single forward slashes and trailing
 
367
        backslashes are removed"""
 
368
        self.assertEqual(u'/', normalize_pattern(u'\\'))
 
369
        self.assertEqual(u'/', normalize_pattern(u'\\\\'))
 
370
        self.assertEqual(u'/foo/bar', normalize_pattern(u'\\foo\\bar'))
 
371
        self.assertEqual(u'foo/bar', normalize_pattern(u'foo\\bar\\'))
 
372
        self.assertEqual(u'/foo/bar', normalize_pattern(u'\\\\foo\\\\bar\\\\'))
 
373
 
 
374
    def test_forward_slashes(self):
 
375
        """tests that multiple foward slashes are collapsed to single forward
 
376
        slashes and trailing forward slashes are removed"""
 
377
        self.assertEqual(u'/', normalize_pattern(u'/'))
 
378
        self.assertEqual(u'/', normalize_pattern(u'//'))
 
379
        self.assertEqual(u'/foo/bar', normalize_pattern(u'/foo/bar'))
 
380
        self.assertEqual(u'foo/bar', normalize_pattern(u'foo/bar/'))
 
381
        self.assertEqual(u'/foo/bar', normalize_pattern(u'//foo//bar//'))
 
382
 
 
383
    def test_mixed_slashes(self):
 
384
        """tests that multiple mixed slashes are collapsed to single forward
 
385
        slashes and trailing mixed slashes are removed"""
 
386
        self.assertEqual(u'/foo/bar', normalize_pattern(u'\\/\\foo//\\///bar/\\\\/'))